From 9ca2424df1f7dae54d5605d10fccbc7620203b04 Mon Sep 17 00:00:00 2001
From: python273
Date: Tue, 22 Oct 2019 20:53:15 +0300
Subject: [PATCH 001/168] Add non-blocking screen updating
Signed-off-by: python273
---
pwnagotchi/ui/display.py | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/pwnagotchi/ui/display.py b/pwnagotchi/ui/display.py
index c0579221..c28b4d75 100644
--- a/pwnagotchi/ui/display.py
+++ b/pwnagotchi/ui/display.py
@@ -1,7 +1,8 @@
import os
import logging
-import pwnagotchi.plugins as plugins
+import threading
+import pwnagotchi.plugins as plugins
import pwnagotchi.ui.hw as hw
import pwnagotchi.ui.web as web
from pwnagotchi.ui.view import View
@@ -18,6 +19,14 @@ class Display(View):
self.init_display()
+ self._canvas_next_event = threading.Event()
+ self._canvas_next = None
+ self._render_thread_instance = threading.Thread(
+ target=self._render_thread,
+ daemon=True
+ )
+ self._render_thread_instance.start()
+
def is_inky(self):
return self._implementation.name == 'inky'
@@ -59,6 +68,14 @@ class Display(View):
img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation)
return img
+ def _render_thread(self):
+ """Used for non-blocking screen updating."""
+
+ while True:
+ self._canvas_next_event.wait()
+ self._canvas_next_event.clear()
+ self._implementation.render(self._canvas_next)
+
def _on_view_rendered(self, img):
web.update_frame(img)
try:
@@ -70,4 +87,5 @@ class Display(View):
if self._enabled:
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
if self._implementation is not None:
- self._implementation.render(self._canvas)
+ self._canvas_next = self._canvas
+ self._canvas_next_event.set()
From 4fa7e9f0779bc202d464db8693629cccd1a840a5 Mon Sep 17 00:00:00 2001
From: python273
Date: Tue, 22 Oct 2019 20:59:15 +0300
Subject: [PATCH 002/168] Add slight delay for waveshare v1 ReadBusy
Signed-off-by: python273
---
.../ui/hw/libs/waveshare/v1/epd2in13bc.py | 31 ++++++++++---------
1 file changed, 16 insertions(+), 15 deletions(-)
diff --git a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py
index a0bff834..f17f0af0 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/v1/epd2in13bc.py
@@ -47,11 +47,11 @@ class EPD:
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
- epdconfig.delay_ms(200)
+ epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
- epdconfig.delay_ms(200)
+ epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
@@ -64,8 +64,9 @@ class EPD:
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
-
+
def ReadBusy(self):
+ epdconfig.delay_ms(20)
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100)
@@ -79,16 +80,16 @@ class EPD:
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
-
+
self.send_command(0x04) # POWER_ON
self.ReadBusy()
-
+
self.send_command(0x00) # PANEL_SETTING
self.send_data(0x8F)
-
+
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0xF0)
-
+
self.send_command(0x61) # RESOLUTION_SETTING
self.send_data(self.width & 0xff)
self.send_data(self.height >> 8)
@@ -120,7 +121,7 @@ class EPD:
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
-
+
self.send_command(0x12) # REFRESH
self.ReadBusy()
@@ -129,26 +130,26 @@ class EPD:
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
-
+
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagecolor[i])
self.send_command(0x92)
-
+
self.send_command(0x12) # REFRESH
self.ReadBusy()
-
+
def Clear(self):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
- self.send_command(0x92)
-
+ self.send_command(0x92)
+
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
-
+
self.send_command(0x12) # REFRESH
self.ReadBusy()
@@ -157,7 +158,7 @@ class EPD:
self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code
-
+
# epdconfig.module_exit()
### END OF FILE ###
From 23ef17d4c79da51ea43b0dffc09647efc8f7184a Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 15:14:55 +0200
Subject: [PATCH 003/168] fix: using normal status to signal unread messages in
order to avoid BT overlap bug
---
pwnagotchi/plugins/default/grid.py | 14 ++------------
pwnagotchi/ui/view.py | 5 +++++
pwnagotchi/voice.py | 4 ++++
3 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index cfb5d8fc..5b32635c 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -66,18 +66,8 @@ def is_excluded(what):
def on_ui_update(ui):
- new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
- if not ui.has_element('mailbox') and UNREAD_MESSAGES > 0:
- if ui.is_inky():
- pos = (80, 0)
- else:
- pos = (100, 0)
- ui.add_element('mailbox',
- LabeledValue(color=BLACK, label='MSG', value=new_value,
- position=pos,
- label_font=fonts.Bold,
- text_font=fonts.Medium))
- ui.set('mailbox', new_value)
+ if UNREAD_MESSAGES > 0:
+ ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
def set_reported(reported, net_id):
diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py
index a2f2d2cf..dcb31a20 100644
--- a/pwnagotchi/ui/view.py
+++ b/pwnagotchi/ui/view.py
@@ -300,6 +300,11 @@ class View(object):
self.set('status', self._voice.on_handshakes(new_shakes))
self.update()
+ def on_unread_messages(self, count, total):
+ self.set('face', faces.EXCITED)
+ self.set('status', self._voice.on_unread_messages(count, total))
+ self.update()
+
def on_rebooting(self):
self.set('face', faces.BROKEN)
self.set('status', self._voice.on_rebooting())
diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py
index 61689cf9..530c815a 100644
--- a/pwnagotchi/voice.py
+++ b/pwnagotchi/voice.py
@@ -129,6 +129,10 @@ class Voice:
s = 's' if new_shakes > 1 else ''
return self._('Cool, we got {num} new handshake{plural}!').format(num=new_shakes, plural=s)
+ def on_unread_messages(self, count, total):
+ s = 's' if count > 1 else ''
+ return self._('You have {count} new message{plural}!').format(num=count, plural=s)
+
def on_rebooting(self):
return self._("Ops, something went wrong ... Rebooting ...")
From 3ad426916f203c5b96afa08a1ba627cad38d2d33 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 15:20:16 +0200
Subject: [PATCH 004/168] small refactoring to facilitate integration of the
bond equation
---
pwnagotchi/defaults.yml | 1 +
pwnagotchi/mesh/peer.py | 50 ++++++++++++++++++++++++++--------------
pwnagotchi/mesh/utils.py | 16 +++++++++----
scripts/backup.sh | 1 +
4 files changed, 46 insertions(+), 22 deletions(-)
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 6bd94351..6e919650 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -31,6 +31,7 @@ main:
- /root/brain.json
- /root/.api-report.json
- /root/handshakes/
+ - /root/peers/
- /etc/pwnagotchi/
- /var/log/pwnagotchi.log
commands:
diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py
index 14f00577..a3ddd553 100644
--- a/pwnagotchi/mesh/peer.py
+++ b/pwnagotchi/mesh/peer.py
@@ -1,17 +1,27 @@
import time
import logging
+import datetime
import pwnagotchi.ui.faces as faces
+def parse_rfc3339(dt):
+ return datetime.datetime.strptime(dt.split('.')[0], "%Y-%m-%dT%H:%M:%S")
+
+
class Peer(object):
def __init__(self, obj):
- self.first_seen = time.time()
- self.last_seen = self.first_seen
- self.session_id = obj['session_id']
- self.last_channel = obj['channel']
- self.rssi = obj['rssi']
- self.adv = obj['advertisement']
+ now = time.time()
+ just_met = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
+ self.first_met = parse_rfc3339(obj.get('met_at', just_met))
+ self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
+ self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
+ self.last_seen = now # should be seen_at
+ self.encounters = obj.get('encounters', 0)
+ self.session_id = obj.get('session_id', '')
+ self.last_channel = obj.get('channel', 1)
+ self.rssi = obj.get('rssi', 0)
+ self.adv = obj.get('advertisement', {})
def update(self, new):
if self.name() != new.name():
@@ -24,39 +34,45 @@ class Peer(object):
self.rssi = new.rssi
self.session_id = new.session_id
self.last_seen = time.time()
+ self.prev_seen = new.prev_seen
+ self.first_met = new.first_met
+ self.encounters = new.encounters
def inactive_for(self):
return time.time() - self.last_seen
- def _adv_field(self, name, default='???'):
- return self.adv[name] if name in self.adv else default
+ def first_encounter(self):
+ return self.encounters == 1
+
+ def days_since_first_met(self):
+ return (datetime.datetime.now() - self.first_met).days
def face(self):
- return self._adv_field('face', default=faces.FRIEND)
+ return self.adv.get('face', faces.FRIEND)
def name(self):
- return self._adv_field('name')
+ return self.adv.get('name')
def identity(self):
- return self._adv_field('identity')
+ return self.adv.get('identity')
def version(self):
- return self._adv_field('version')
+ return self.adv.get('version')
def pwnd_run(self):
- return int(self._adv_field('pwnd_run', default=0))
+ return int(self.adv.get('pwnd_run', 0))
def pwnd_total(self):
- return int(self._adv_field('pwnd_tot', default=0))
+ return int(self.adv.get('pwnd_tot', 0))
def uptime(self):
- return self._adv_field('uptime', default=0)
+ return self.adv.get('uptime', 0)
def epoch(self):
- return self._adv_field('epoch', default=0)
+ return self.adv.get('epoch', 0)
def full_name(self):
return '%s@%s' % (self.name(), self.identity())
def is_closer(self, other):
- return self.rssi > other.rssi
+ return self.rssi > other.rssi
\ No newline at end of file
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 3849acc5..8591f4bf 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -53,6 +53,14 @@ class AsyncAdvertiser(object):
self._advertisement['face'] = new
grid.set_advertisement_data(self._advertisement)
+ def _on_new_peer(self, peer):
+ self._view.on_new_peer(peer)
+ plugins.on('peer_detected', self, peer)
+
+ def _on_lost_peer(self, peer):
+ self._view.on_lost_peer(peer)
+ plugins.on('peer_lost', self, peer)
+
def _adv_poller(self):
while True:
logging.debug("polling pwngrid-peer for peers ...")
@@ -72,24 +80,22 @@ class AsyncAdvertiser(object):
to_delete = []
for ident, peer in self._peers.items():
if ident not in new_peers:
- self._view.on_lost_peer(peer)
- plugins.on('peer_lost', self, peer)
to_delete.append(ident)
for ident in to_delete:
+ self._on_lost_peer(peer)
del self._peers[ident]
for ident, peer in new_peers.items():
# check who's new
if ident not in self._peers:
self._peers[ident] = peer
- self._view.on_new_peer(peer)
- plugins.on('peer_detected', self, peer)
+ self._on_new_peer(peer)
# update the rest
else:
self._peers[ident].update(peer)
except Exception as e:
- logging.exception("error while polling pwngrid-peer")
+ logging.warning("error while polling pwngrid-peer: %s" % e)
time.sleep(1)
diff --git a/scripts/backup.sh b/scripts/backup.sh
index f66ca783..71c4f612 100755
--- a/scripts/backup.sh
+++ b/scripts/backup.sh
@@ -10,6 +10,7 @@ FILES_TO_BACKUP=(
/root/brain.json
/root/.api-report.json
/root/handshakes
+ /root/peers
/etc/pwnagotchi/
/var/log/pwnagotchi.log
)
From a78a4b0b3e5cfbdfea481e140e6c02e0bdb1f72d Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 15:26:53 +0200
Subject: [PATCH 005/168] fix: fixes a race condition in the launcher scripts
and enables MANU if eth0 is up (closes #365)
---
builder/pwnagotchi.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml
index a9fb322c..b4c9680a 100644
--- a/builder/pwnagotchi.yml
+++ b/builder/pwnagotchi.yml
@@ -304,7 +304,7 @@
# blink 10 times to signal ready state
/usr/bin/bootblink 10 &
# start a detached screen session with bettercap
- if ifconfig | grep usb0 | grep RUNNING; then
+ if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
# if override file exists, go into auto mode
if [ -f /root/.pwnagotchi-auto ]; then
rm /root/.pwnagotchi-auto
@@ -322,13 +322,10 @@
mode: 0755
content: |
#!/usr/bin/env bash
- # blink 10 times to signal ready state
- /usr/bin/bootblink 10 &
/usr/bin/monstart
- if ifconfig | grep usb0 | grep RUNNING; then
+ if [[ ifconfig | grep usb0 | grep RUNNING ]] || [[ $(cat /sys/class/net/eth0/carrier) ]]; then
# if override file exists, go into auto mode
if [ -f /root/.pwnagotchi-auto ]; then
- rm /root/.pwnagotchi-auto
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0
From 277906a6738553263db0174d003dd1367137423e Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 15:37:12 +0200
Subject: [PATCH 006/168] fix: fixed bogus support for waveshare lcd displays
(fixes #364)
---
.../ui/hw/libs/waveshare/lcdhat/ST7789.py | 85 ++++++++++---------
.../ui/hw/libs/waveshare/lcdhat/config.py | 61 +------------
pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py | 21 ++---
3 files changed, 53 insertions(+), 114 deletions(-)
diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py
index 0edd1d12..88409ecc 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/ST7789.py
@@ -7,24 +7,25 @@ import numpy as np
class ST7789(object):
"""class for ST7789 240*240 1.3inch OLED displays."""
- def __init__(self,spi,rst = 27,dc = 25,bl = 24):
+ def __init__(self, spi, rst=27, dc=25, bl=24):
self.width = 240
self.height = 240
- #Initialize DC RST pin
+ # Initialize DC RST pin
self._dc = dc
self._rst = rst
self._bl = bl
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
- GPIO.setup(self._dc,GPIO.OUT)
- GPIO.setup(self._rst,GPIO.OUT)
- GPIO.setup(self._bl,GPIO.OUT)
+ GPIO.setup(self._dc, GPIO.OUT)
+ GPIO.setup(self._rst, GPIO.OUT)
+ GPIO.setup(self._bl, GPIO.OUT)
GPIO.output(self._bl, GPIO.HIGH)
- #Initialize SPI
+ # Initialize SPI
self._spi = spi
self._spi.max_speed_hz = 40000000
""" Write register address and data """
+
def command(self, cmd):
GPIO.output(self._dc, GPIO.LOW)
self._spi.writebytes([cmd])
@@ -34,13 +35,13 @@ class ST7789(object):
self._spi.writebytes([val])
def Init(self):
- """Initialize dispaly"""
+ """Initialize dispaly"""
self.reset()
self.command(0x36)
- self.data(0x70) #self.data(0x00)
+ self.data(0x70) # self.data(0x00)
- self.command(0x3A)
+ self.command(0x3A)
self.data(0x05)
self.command(0xB2)
@@ -51,7 +52,7 @@ class ST7789(object):
self.data(0x33)
self.command(0xB7)
- self.data(0x35)
+ self.data(0x35)
self.command(0xBB)
self.data(0x19)
@@ -63,13 +64,13 @@ class ST7789(object):
self.data(0x01)
self.command(0xC3)
- self.data(0x12)
+ self.data(0x12)
self.command(0xC4)
self.data(0x20)
self.command(0xC6)
- self.data(0x0F)
+ self.data(0x0F)
self.command(0xD0)
self.data(0xA4)
@@ -106,7 +107,7 @@ class ST7789(object):
self.data(0x1F)
self.data(0x20)
self.data(0x23)
-
+
self.command(0x21)
self.command(0x11)
@@ -115,51 +116,51 @@ class ST7789(object):
def reset(self):
"""Reset the display"""
- GPIO.output(self._rst,GPIO.HIGH)
+ GPIO.output(self._rst, GPIO.HIGH)
time.sleep(0.01)
- GPIO.output(self._rst,GPIO.LOW)
+ GPIO.output(self._rst, GPIO.LOW)
time.sleep(0.01)
- GPIO.output(self._rst,GPIO.HIGH)
+ GPIO.output(self._rst, GPIO.HIGH)
time.sleep(0.01)
-
+
def SetWindows(self, Xstart, Ystart, Xend, Yend):
- #set the X coordinates
+ # set the X coordinates
self.command(0x2A)
- self.data(0x00) #Set the horizontal starting point to the high octet
- self.data(Xstart & 0xff) #Set the horizontal starting point to the low octet
- self.data(0x00) #Set the horizontal end to the high octet
- self.data((Xend - 1) & 0xff) #Set the horizontal end to the low octet
-
- #set the Y coordinates
+ self.data(0x00) # Set the horizontal starting point to the high octet
+ self.data(Xstart & 0xff) # Set the horizontal starting point to the low octet
+ self.data(0x00) # Set the horizontal end to the high octet
+ self.data((Xend - 1) & 0xff) # Set the horizontal end to the low octet
+
+ # set the Y coordinates
self.command(0x2B)
self.data(0x00)
self.data((Ystart & 0xff))
self.data(0x00)
- self.data((Yend - 1) & 0xff )
+ self.data((Yend - 1) & 0xff)
- self.command(0x2C)
-
- def ShowImage(self,Image,Xstart,Ystart):
+ self.command(0x2C)
+
+ def ShowImage(self, Image, Xstart, Ystart):
"""Set buffer to value of Python Imaging Library image."""
"""Write display buffer to physical display"""
imwidth, imheight = Image.size
if imwidth != self.width or imheight != self.height:
raise ValueError('Image must be same dimensions as display \
- ({0}x{1}).' .format(self.width, self.height))
+ ({0}x{1}).'.format(self.width, self.height))
img = np.asarray(Image)
- pix = np.zeros((self.width,self.height,2), dtype = np.uint8)
- pix[...,[0]] = np.add(np.bitwise_and(img[...,[0]],0xF8),np.right_shift(img[...,[1]],5))
- pix[...,[1]] = np.add(np.bitwise_and(np.left_shift(img[...,[1]],3),0xE0),np.right_shift(img[...,[2]],3))
+ pix = np.zeros((self.width, self.height, 2), dtype=np.uint8)
+ pix[..., [0]] = np.add(np.bitwise_and(img[..., [0]], 0xF8), np.right_shift(img[..., [1]], 5))
+ pix[..., [1]] = np.add(np.bitwise_and(np.left_shift(img[..., [1]], 3), 0xE0), np.right_shift(img[..., [2]], 3))
pix = pix.flatten().tolist()
- self.SetWindows ( 0, 0, self.width, self.height)
- GPIO.output(self._dc,GPIO.HIGH)
- for i in range(0,len(pix),4096):
- self._spi.writebytes(pix[i:i+4096])
-
+ self.SetWindows(0, 0, self.width, self.height)
+ GPIO.output(self._dc, GPIO.HIGH)
+ for i in range(0, len(pix), 4096):
+ self._spi.writebytes(pix[i:i + 4096])
+
def clear(self):
"""Clear contents of image buffer"""
- _buffer = [0xff]*(self.width * self.height * 2)
- self.SetWindows ( 0, 0, self.width, self.height)
- GPIO.output(self._dc,GPIO.HIGH)
- for i in range(0,len(_buffer),4096):
- self._spi.writebytes(_buffer[i:i+4096])
+ _buffer = [0xff] * (self.width * self.height * 2)
+ self.SetWindows(0, 0, self.width, self.height)
+ GPIO.output(self._dc, GPIO.HIGH)
+ for i in range(0, len(_buffer), 4096):
+ self._spi.writebytes(_buffer[i:i + 4096])
diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py
index a319364f..24b5cc88 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/config.py
@@ -7,70 +7,15 @@
# * | Date : 2019-10-18
# * | Info :
# ******************************************************************************/
-
-import RPi.GPIO as GPIO
-import time
-from smbus import SMBus
import spidev
-import ctypes
-# import spidev
-
# Pin definition
-RST_PIN = 27
-DC_PIN = 25
-BL_PIN = 24
+RST_PIN = 27
+DC_PIN = 25
+BL_PIN = 24
Device_SPI = 1
Device_I2C = 0
Device = Device_SPI
spi = spidev.SpiDev(0, 0)
-
-def digital_write(pin, value):
- GPIO.output(pin, value)
-
-def digital_read(pin):
- return GPIO.input(BUSY_PIN)
-
-def delay_ms(delaytime):
- time.sleep(delaytime / 1000.0)
-
-def spi_writebyte(data):
- # SPI.writebytes(data)
- spi.writebytes([data[0]])
-
-def i2c_writebyte(reg, value):
- bus.write_byte_data(address, reg, value)
-
- # time.sleep(0.01)
-def module_init():
- # print("module_init")
-
- GPIO.setmode(GPIO.BCM)
- GPIO.setwarnings(False)
- GPIO.setup(RST_PIN, GPIO.OUT)
- GPIO.setup(DC_PIN, GPIO.OUT)
-
-
- # SPI.max_speed_hz = 2000000
- # SPI.mode = 0b00
- # i2c_writebyte(0xff,0xff)
- # spi.SYSFS_software_spi_begin()
- # spi.SYSFS_software_spi_setDataMode(0);
- # spi.SYSFS_software_spi_setClockDivider(1);
- #spi.max_speed_hz = 2000000
- #spi.mode = 0b00
-
- GPIO.output(BL_PIN, 1)
- GPIO.output(DC_PIN, 0)
- return 0
-
-def module_exit():
- spi.SYSFS_software_spi_end()
- GPIO.output(RST_PIN, 0)
- GPIO.output(DC_PIN, 0)
-
-
-
-### END OF FILE ###
diff --git a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py
index cd0d81f4..1a559f46 100644
--- a/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py
+++ b/pwnagotchi/ui/hw/libs/waveshare/lcdhat/epd.py
@@ -1,28 +1,21 @@
from . import ST7789
from . import config
-# Display resolution
-EPD_WIDTH = 240
-EPD_HEIGHT = 240
-
-disp = ST7789.ST7789(config.spi,config.RST_PIN, config.DC_PIN, config.BL_PIN)
class EPD(object):
-
def __init__(self):
self.reset_pin = config.RST_PIN
self.dc_pin = config.DC_PIN
- #self.busy_pin = config.BUSY_PIN
- #self.cs_pin = config.CS_PIN
- self.width = EPD_WIDTH
- self.height = EPD_HEIGHT
+ self.width = 240
+ self.height = 240
+ self.st7789 = ST7789.ST7789(config.spi, config.RST_PIN, config.DC_PIN, config.BL_PIN)
def init(self):
- disp.Init()
+ self.st7789.Init()
- def Clear(self):
- disp.clear()
+ def clear(self):
+ self.st7789.clear()
def display(self, image):
rgb_im = image.convert('RGB')
- disp.ShowImage(rgb_im,0,0)
+ self.st7789.ShowImage(rgb_im, 0, 0)
From 062de3b61888fc0d8f055059dea1899ed15c67d3 Mon Sep 17 00:00:00 2001
From: Zenzen San <56201767+zenzen666@users.noreply.github.com>
Date: Wed, 23 Oct 2019 15:59:11 +0000
Subject: [PATCH 007/168] minor logging correction
`on loaded` the plugin was showing a different name: `cleancap plugin loaded`
I've changed it with the name of the plugin to be consistent and easy to find what going on from pwnagotchi.log
---
pwnagotchi/plugins/default/AircrackOnly.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py
index b1baa999..a8402097 100644
--- a/pwnagotchi/plugins/default/AircrackOnly.py
+++ b/pwnagotchi/plugins/default/AircrackOnly.py
@@ -18,7 +18,7 @@ import os
OPTIONS = dict()
def on_loaded():
- logging.info("cleancap plugin loaded")
+ logging.info("aircrackonly plugin loaded")
def on_handshake(agent, filename, access_point, client_station):
display = agent._view
From 36c3ea5bbc12c6b7e58cd36bc04fbbc5e6bbd994 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 18:19:43 +0200
Subject: [PATCH 008/168] new: new grateful status that can override
sad/bored/lonely if units with a strong bond are nearby
---
pwnagotchi/agent.py | 100 +++------------------------------
pwnagotchi/ai/train.py | 2 -
pwnagotchi/automata.py | 118 +++++++++++++++++++++++++++++++++++++++
pwnagotchi/defaults.yml | 4 ++
pwnagotchi/mesh/utils.py | 3 +
pwnagotchi/ui/faces.py | 1 +
pwnagotchi/ui/view.py | 5 ++
pwnagotchi/voice.py | 5 ++
8 files changed, 144 insertions(+), 94 deletions(-)
create mode 100644 pwnagotchi/automata.py
diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py
index 16fa722a..b3dd8cf4 100644
--- a/pwnagotchi/agent.py
+++ b/pwnagotchi/agent.py
@@ -8,6 +8,7 @@ import _thread
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
+from pwnagotchi.automata import Automata
from pwnagotchi.log import LastSession
from pwnagotchi.bettercap import Client
from pwnagotchi.mesh.utils import AsyncAdvertiser
@@ -16,13 +17,14 @@ from pwnagotchi.ai.train import AsyncTrainer
RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
-class Agent(Client, AsyncAdvertiser, AsyncTrainer):
+class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def __init__(self, view, config, keypair):
Client.__init__(self, config['bettercap']['hostname'],
config['bettercap']['scheme'],
config['bettercap']['port'],
config['bettercap']['username'],
config['bettercap']['password'])
+ Automata.__init__(self, config, view)
AsyncAdvertiser.__init__(self, config, view, keypair)
AsyncTrainer.__init__(self, config)
@@ -49,36 +51,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def supported_channels(self):
return self._supported_channels
- def set_starting(self):
- self._view.on_starting()
-
- def set_ready(self):
- plugins.on('ready', self)
-
- def set_free_channel(self, channel):
- self._view.on_free_channel(channel)
- plugins.on('free_channel', self, channel)
-
- def set_bored(self):
- self._view.on_bored()
- plugins.on('bored', self)
-
- def set_sad(self):
- self._view.on_sad()
- plugins.on('sad', self)
-
- def set_excited(self):
- self._view.on_excited()
- plugins.on('excited', self)
-
- def set_lonely(self):
- self._view.on_lonely()
- plugins.on('lonely', self)
-
- def set_rebooting(self):
- self._view.on_rebooting()
- plugins.on('rebooting', self)
-
def setup_events(self):
logging.info("connecting to %s ..." % self.url)
@@ -155,11 +127,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self.next_epoch()
self.set_ready()
- def wait_for(self, t, sleeping=True):
- plugins.on('sleep' if sleeping else 'wait', self, t)
- self._view.wait(t, sleeping)
- self._epoch.track(sleep=True, inc=t)
-
def recon(self):
recon_time = self._config['personality']['recon_time']
max_inactive = self._config['personality']['max_inactive_scale']
@@ -277,6 +244,11 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def _update_peers(self):
self._view.set_closest_peer(self._closest_peer, len(self._peers))
+ def _reboot(self):
+ self.set_rebooting()
+ self._save_recovery_data()
+ pwnagotchi.reboot()
+
def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
with open(RECOVERY_DATA_FILE, 'w') as fp:
@@ -391,21 +363,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return self._history[who] < self._config['personality']['max_interactions']
- def _on_miss(self, 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:
- self._on_miss(who)
- else:
- logging.error("%s" % e)
-
def associate(self, ap, throttle=0):
if self.is_stale():
logging.debug("recon is stale, skipping assoc(%s)" % ap['mac'])
@@ -482,44 +439,3 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
except Exception as e:
logging.error("error: %s" % e)
-
- def is_stale(self):
- return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
-
- def any_activity(self):
- return self._epoch.any_activity
-
- def _reboot(self):
- self.set_rebooting()
- self._save_recovery_data()
- pwnagotchi.reboot()
-
- def next_epoch(self):
- was_stale = self.is_stale()
- did_miss = self._epoch.num_missed
-
- self._epoch.next()
-
- # after X misses during an epoch, set the status to lonely
- if was_stale:
- logging.warning("agent missed %d interactions -> lonely" % did_miss)
- self.set_lonely()
- # after X times being bored, the status is set to sad
- elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
- logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
- self.set_sad()
- # after X times being inactive, the status is set to bored
- elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
- logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
- self.set_bored()
- # after X times being active, the status is set to happy / excited
- elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
- logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
- self.set_excited()
-
- 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)
- self._reboot()
- self._epoch.blind_for = 0
diff --git a/pwnagotchi/ai/train.py b/pwnagotchi/ai/train.py
index 96e6789d..ed644d6d 100644
--- a/pwnagotchi/ai/train.py
+++ b/pwnagotchi/ai/train.py
@@ -8,7 +8,6 @@ import logging
import pwnagotchi.plugins as plugins
import pwnagotchi.ai as ai
-from pwnagotchi.ai.epoch import Epoch
class Stats(object):
@@ -88,7 +87,6 @@ class AsyncTrainer(object):
def __init__(self, config):
self._config = config
self._model = None
- self._epoch = Epoch(config)
self._is_training = False
self._training_epochs = 0
self._nn_path = self._config['ai']['path']
diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py
new file mode 100644
index 00000000..b0ae92fd
--- /dev/null
+++ b/pwnagotchi/automata.py
@@ -0,0 +1,118 @@
+import logging
+
+import pwnagotchi.plugins as plugins
+from pwnagotchi.ai.epoch import Epoch
+
+
+# basic mood system
+class Automata(object):
+ def __init__(self, config, view):
+ self._config = config
+ self._view = view
+ self._epoch = Epoch(config)
+
+ def _on_miss(self, 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:
+ self._on_miss(who)
+ else:
+ logging.error("%s" % e)
+
+ def set_starting(self):
+ self._view.on_starting()
+
+ def set_ready(self):
+ plugins.on('ready', self)
+
+ def _has_support_network_for(self, factor):
+ bond_factor = self._config['personality']['bond_encounters_factor']
+ total_encounters = sum(peer.encounters for _, peer in self._peers.items())
+ support_factor = total_encounters / bond_factor
+ return support_factor >= factor
+
+ # triggered when it's a sad/bad day but you have good friends around ^_^
+ def set_grateful(self):
+ self._view.on_grateful()
+ plugins.on('grateful', self)
+
+ def set_lonely(self):
+ if not self._has_support_network_for(1.0):
+ self._view.on_lonely()
+ plugins.on('lonely', self)
+ else:
+ self.set_grateful()
+
+ 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)
+ self._view.on_bored()
+ plugins.on('bored', self)
+ else:
+ self.set_grateful()
+
+ 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)
+ self._view.on_sad()
+ plugins.on('sad', self)
+ else:
+ self.set_grateful()
+
+ def set_excited(self):
+ logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
+ self._view.on_excited()
+ plugins.on('excited', self)
+
+ def set_rebooting(self):
+ self._view.on_rebooting()
+ plugins.on('rebooting', self)
+
+ def wait_for(self, t, sleeping=True):
+ plugins.on('sleep' if sleeping else 'wait', self, t)
+ self._view.wait(t, sleeping)
+ self._epoch.track(sleep=True, inc=t)
+
+ def is_stale(self):
+ return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
+
+ def any_activity(self):
+ return self._epoch.any_activity
+
+ def next_epoch(self):
+ was_stale = self.is_stale()
+ did_miss = self._epoch.num_missed
+
+ self._epoch.next()
+
+ # after X misses during an epoch, set the status to lonely
+ if was_stale:
+ logging.warning("agent missed %d interactions -> lonely" % did_miss)
+ self.set_lonely()
+ # after X times being bored, the status is set to sad
+ elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
+ self.set_sad()
+ # after X times being inactive, the status is set to bored
+ elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
+ self.set_bored()
+ # after X times being active, the status is set to happy / excited
+ elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
+ self.set_excited()
+ elif self._has_support_network_for(1.0):
+ self.set_grateful()
+
+ 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)
+ self._reboot()
+ self._epoch.blind_for = 0
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 6e919650..a22874cf 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -161,6 +161,9 @@ personality:
bored_num_epochs: 15
# number of inactive epochs that triggers the sad state
sad_num_epochs: 25
+ # number of encounters (times met on a channel) with another unit before considering it a friend and bond
+ # also used for cumulative bonding score of nearby units
+ bond_encounters_factor: 5000
# ui configuration
ui:
@@ -176,6 +179,7 @@ ui:
cool: '(⌐■_■)'
happy: '(•‿‿•)'
excited: '(ᵔ◡◡ᵔ)'
+ grateful: '(^‿‿^)'
motivated: '(☼‿‿☼)'
demotivated: '(≖__≖)'
smart: '(✜‿‿✜)'
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 8591f4bf..9a67f38f 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -53,6 +53,9 @@ class AsyncAdvertiser(object):
self._advertisement['face'] = new
grid.set_advertisement_data(self._advertisement)
+ def cumulative_encounters(self):
+ return sum(peer.encounters for _, peer in self._peers.items())
+
def _on_new_peer(self, peer):
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py
index d4cbd32a..d90b5498 100644
--- a/pwnagotchi/ui/faces.py
+++ b/pwnagotchi/ui/faces.py
@@ -7,6 +7,7 @@ BORED = '(-__-)'
INTENSE = '(°▃▃°)'
COOL = '(⌐■_■)'
HAPPY = '(•‿‿•)'
+GRATEFUL = '(^‿‿^)'
EXCITED = '(ᵔ◡◡ᵔ)'
MOTIVATED = '(☼‿‿☼)'
DEMOTIVATED = '(≖__≖)'
diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py
index dcb31a20..3542c8db 100644
--- a/pwnagotchi/ui/view.py
+++ b/pwnagotchi/ui/view.py
@@ -290,6 +290,11 @@ class View(object):
self.set('status', self._voice.on_miss(who))
self.update()
+ def on_grateful(self):
+ self.set('face', faces.GRATEFUL)
+ self.set('status', self._voice.on_grateful())
+ self.update()
+
def on_lonely(self):
self.set('face', faces.LONELY)
self.set('status', self._voice.on_lonely())
diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py
index 530c815a..cf98fdad 100644
--- a/pwnagotchi/voice.py
+++ b/pwnagotchi/voice.py
@@ -85,6 +85,11 @@ class Voice:
self._('{name} missed!').format(name=who),
self._('Missed!')])
+ def on_grateful(self):
+ return random.choice([
+ self._('Good friends are a blessing!'),
+ self._('I love my friends!')])
+
def on_lonely(self):
return random.choice([
self._('Nobody wants to play with me ...'),
From f734c9cd68d94fb48602dc4267965ff5acbe0645 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 18:21:23 +0200
Subject: [PATCH 009/168] new: bumped pwngrid required version to 1.10.0
---
builder/pwnagotchi.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/builder/pwnagotchi.yml b/builder/pwnagotchi.yml
index b4c9680a..44df05f7 100644
--- a/builder/pwnagotchi.yml
+++ b/builder/pwnagotchi.yml
@@ -36,7 +36,7 @@
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid:
- url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.9.0.zip"
+ url: "https://github.com/evilsocket/pwngrid/releases/download/v1.9.0/pwngrid_linux_armhf_v1.10.0.zip"
apt:
hold:
- firmware-atheros
From f2ceec0f8f54d33d6953068f52ccf13e138f55e0 Mon Sep 17 00:00:00 2001
From: Xavier Stouder
Date: Wed, 23 Oct 2019 18:47:47 +0200
Subject: [PATCH 010/168] fix: update french translation
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 27 +++++++++++++++++------
pwnagotchi/locale/voice.pot | 6 ++++-
2 files changed, 25 insertions(+), 8 deletions(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index 26f46788..7109a70c 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -3,12 +3,12 @@
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019.
#
-#, fuzzy
+#,
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-05 14:10+0200\n"
+"POT-Creation-Date: 2019-10-23 18:37+0200\n"
"PO-Revision-Date: 2019-10-03 10:34+0200\n"
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
"com>\n"
@@ -19,7 +19,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
-msgstr ""
+msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
@@ -36,6 +36,9 @@ msgstr "L'IA est prête."
msgid "The neural network is ready."
msgstr "Le réseau neuronal est prêt."
+msgid "Generating keys, do not turn off ..."
+msgstr "Génération des clés, ne pas éteindre..."
+
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
@@ -77,12 +80,12 @@ msgid "My crime is that of curiosity ..."
msgstr "Mon crime, c'est la curiosité ..."
#, python-brace-format
-msgid "Hello {name}! Nice to meet you. {name}"
-msgstr "Bonjour {name}! Ravi de te rencontrer. {name}"
+msgid "Hello {name}! Nice to meet you."
+msgstr "Bonjour {name}! Ravi de te rencontrer."
#, python-brace-format
-msgid "Unit {name} is nearby! {name}"
-msgstr "L'unité {name} est proche! {name}"
+msgid "Unit {name} is nearby!"
+msgstr "L'unité {name} est proche!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
@@ -123,6 +126,12 @@ msgstr ""
msgid "ZzzZzzz ({secs}s)"
msgstr ""
+msgid "Good night."
+msgstr "Bonne nuit."
+
+msgid "Zzz"
+msgstr "Zzz"
+
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Attends pendant {secs}s ..."
@@ -159,6 +168,10 @@ msgstr "Je kick et je bannis {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, on a {num} nouveaux handshake{plural}!"
+#, python-brace-format
+msgid "You have {count} new message{plural}!"
+msgstr "Tu as {num} nouveaux message{plural}!"
+
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot
index 10de71fe..7d20973c 100644
--- a/pwnagotchi/locale/voice.pot
+++ b/pwnagotchi/locale/voice.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-21 10:49+0200\n"
+"POT-Creation-Date: 2019-10-23 18:37+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -167,6 +167,10 @@ msgstr ""
msgid "Cool, we got {num} new handshake{plural}!"
msgstr ""
+#, python-brace-format
+msgid "You have {count} new message{plural}!"
+msgstr ""
+
msgid "Ops, something went wrong ... Rebooting ..."
msgstr ""
From ab871f9d2d444ddd0c7b3e7936781080e6ff043c Mon Sep 17 00:00:00 2001
From: Xavier Stouder
Date: Wed, 23 Oct 2019 18:52:40 +0200
Subject: [PATCH 011/168] fix: fix type of ellpsis in french
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 3922 -> 4512 bytes
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 26 +++++++++++-----------
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index 2ff57511228209251c6d8f3e3f1de75b9ff0d139..d447e5253257b4bd7906e3678c22bd65d8383c1f 100644
GIT binary patch
delta 1996
zcma*nTZmOv9LMoBI_Wq!FL}#sW{sncqtj8#QX~VF(n3;GCi219p0mc;&N+Jz`!ecG
z(_j!3J?SPwh_D{QLl>p#A$ln|^rR8kLk7|neDS3qNPURDzwI`B30lnBpR?E5d#(Tf
zzt)*6UFUkIeq7M}mZ5E-FQ;E?GG;HnGm{JLYO^tO@CRIkzhOV#!o}EopD`~y@jo;Z|1icGnawo$e0H$;$+wno%hx)!+jK5lVx`X-1r@+w_yEph-xT0lRH>gpRp1qRXRpj3GFgII@0R^p0Ni+{Y!|Ulf?uW8P~-OhiQ~wVpH>j
zG;sy7iCsAiDh2WMP|J8z%bvbxwp$k`*{^d}HHU%qviYssH}7iHgGOEXFjR4|Ems?L
zk_wz1^r)GO-X_jQE?~7Ja#1O}*t)Z8H1vbSR{Feh
zxp6+WW8R&IYA!1KF3M)K^)J?J&pMK{u?u1w`Zr`_%7sh-&&aw=b{R*>v*xtL0FGl?Gh~@EP&gNB)?-V(^+6@}Y#J}sH0rD|CfcyEBAB+IE{)Vmyh8OQAcx&Co^tl=axSU$
z76=3?rBE1D^hFYLDB}M+yH)7Gncq3HbH4fJ
zo3rP8A1<5xtE=&(q78EOa2-u4^*R38Ku7y^j#4f78!p60xEf#JBAnk?8()K+Ja5KT
zxEm$jAsoh&sPR6|$5%+aNj0ZQsWbyi@I&mcy9pQYybqhvLs=lgUvL^_q0Mt^iHxJn
zKaPvhqQtw5t#}(V_y8r|6U?%{dd`hZY++O~&Z5SR_%ZHBN$>}h%+H|AyMh|;pj6@|
zF2#mQz$#V
zil5>gl!P1GY6-1HsmPZoJ0C+y+(TLC1WrmO=eQAnMhSQmZ{hE_0Z+Bp0^LV>@fpgz
ze{czQQ65Qb5Tz0uk)o=vaRA3~1y189coBQ?k2Lj{%-=8|nGTbMEU+DyVF9x^g|grU
zlt4G}BYcW-bpN81cmdm#I73KCHG&j4c}FAPTv8!Oe(#m@mj&d*lAXxQ@^wpNbBb>)
zX>x8HZ?a3O{I0FxZY38dn6$pSf+QwY;E$5DL3$~_R7j3hzK!ltI@yWrx}S@qPtJ(uP^<}0cHMyzSsI-6v#Pya{S2t*wJh6b)4?eCX95!c(E&ad5$i`fm;sUh!^ak
z%!)=(_ZtlAk@k_q(~$()K4L~c&i&guO~5~n^GIBYSi-U6X_=P>{P
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index 7109a70c..b891adf8 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -22,7 +22,7 @@ msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
-msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
+msgstr "Bonjour, je suis Pwnagotchi! Démarrage..."
msgid "New day, new hunt, new pwns!"
msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !"
@@ -44,7 +44,7 @@ msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
msgid "I'm bored ..."
-msgstr "Je m'ennuie ..."
+msgstr "Je m'ennuie..."
msgid "Let's go for a walk!"
msgstr "Allons faire un tour!"
@@ -56,10 +56,10 @@ msgid "Shitty day :/"
msgstr "Journée de merde :/"
msgid "I'm extremely bored ..."
-msgstr "Je m'ennuie énormément ..."
+msgstr "Je m'ennuie énormément..."
msgid "I'm very sad ..."
-msgstr "Je suis très triste ..."
+msgstr "Je suis très triste..."
msgid "I'm sad"
msgstr "Je suis triste"
@@ -77,7 +77,7 @@ msgid "I'm having so much fun!"
msgstr "Je m'amuse tellement!"
msgid "My crime is that of curiosity ..."
-msgstr "Mon crime, c'est la curiosité ..."
+msgstr "Mon crime, c'est la curiosité..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
@@ -89,15 +89,15 @@ msgstr "L'unité {name} est proche!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
-msgstr "Hum ... au revoir {name}"
+msgstr "Hum... au revoir {name}"
#, python-brace-format
msgid "{name} is gone ..."
-msgstr "{name} est parti ..."
+msgstr "{name} est part ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
-msgstr "Oups ... {name} est parti."
+msgstr "Oups... {name} est parti."
#, python-brace-format
msgid "{name} missed!"
@@ -107,17 +107,17 @@ msgid "Missed!"
msgstr "Raté!"
msgid "Nobody wants to play with me ..."
-msgstr "Personne ne veut jouer avec moi ..."
+msgstr "Personne ne veut jouer avec moi..."
msgid "I feel so alone ..."
-msgstr "Je me sens si seul ..."
+msgstr "Je me sens si seul..."
msgid "Where's everybody?!"
msgstr "Où est tout le monde?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
-msgstr "Fais la sieste pendant {secs}s ..."
+msgstr "Fais la sieste pendant {secs}s..."
msgid "Zzzzz"
msgstr ""
@@ -134,7 +134,7 @@ msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
-msgstr "Attends pendant {secs}s ..."
+msgstr "Attends pendant {secs}s..."
#, python-brace-format
msgid "Looking around ({secs}s)"
@@ -173,7 +173,7 @@ msgid "You have {count} new message{plural}!"
msgstr "Tu as {num} nouveaux message{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
-msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
+msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
#, python-brace-format
msgid "Kicked {num} stations\n"
From 202cd1f32e32cb1d1eebdaccc982a579b60f4f5c Mon Sep 17 00:00:00 2001
From: Xavier Stouder
Date: Wed, 23 Oct 2019 18:54:12 +0200
Subject: [PATCH 012/168] fix: translate a word
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4512 -> 4510 bytes
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index d447e5253257b4bd7906e3678c22bd65d8383c1f..317fdefb735b3a2445b05c0a1ed7c24087a1e9e4 100644
GIT binary patch
delta 381
zcmXZYKTCp96vy#HkEVh?J?7sGf_zZq8Jwg*1)Us=z`&tJ8iaxt2O$D|2f8>2A<}LL
zg0pM&B3xQpq@}IF@4-7@lo!@2K~EqRwwHjeoZE<%ErWBof5}Cb5i%c!THIGCpIG`W3VI
zg_pQReIW1bFRtJjbrW^IiyCl@8fcCg{BSz^gKrudNjLq^MqbsVzVPwnL*{Oj8R-P6Vt*x1>L@8DL?&J45rvBUT%K6|_9e2K`|D`Mjg0~ov(xyKjuVHzWtMVy1&5?9(>Y~M-G->k55=Iw(ymDh%sE_
z2kucHc(%?LCoxL>jk@1J4cJEwG{rD3t;%_@rJ+HNn7|Ne9GF7FN(Nn+Lk;$0EaD|~
f*;vCX>IU9n8~F_B%?FNKkE>d$*UR&Xckes_aH=OK
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index b891adf8..6abda55f 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -41,7 +41,7 @@ msgstr "Génération des clés, ne pas éteindre..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
-msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
+msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier."
msgid "I'm bored ..."
msgstr "Je m'ennuie..."
From 1d53dc7c4ed330da0e228478e37b831fdd80dc01 Mon Sep 17 00:00:00 2001
From: Xavier Stouder
Date: Wed, 23 Oct 2019 18:58:23 +0200
Subject: [PATCH 013/168] fix: fix space before !
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4510 -> 4525 bytes
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 30 +++++++++++-----------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index 317fdefb735b3a2445b05c0a1ed7c24087a1e9e4..275d056f743de4a049cddd0a73d894ef3df7d4fc 100644
GIT binary patch
delta 539
zcmXZWPbhMsV(s8xks_BZwG(lfl*?L)
z6H!Xa&4sJ9i{h*}I5_xI^8VUWPoLL*zvtQa`zb|EBj+EEd`u~I=25B*pRfmikVi$m
z)%yYTb53CtGuVvVIEqKujc?e9K~1R!jG>N0*or9(;1c?u>_YIBQ$LUe3qZffZlAy6BS&Sr9j>14po(
z^EAqU97@45N}(%k!3s)|ca$P-T6AIrotQv6s&TBrNvy?;?R*zMr0I(72D&(JVm)r5
z3@BY3I4zgMEIxYn$z^YxEPJ2*W>_V%=GxqL>1^9>+sI%zoQEBi!JEociFHX$tVV~1jkVYo3@;9Wnrfgw@hIfbp|VN
z6Zsip|N7V#E|brWBf0GClxNOY-6uv&V>OX67NcOw11%!Iv>7>M56P^nyWxL7m58Sd
tbHzxSMl706b9!Sb8sCzSu3fpH=jEJxO+LB1WT_`CAIrTm;u$UWi9gDuO
Date: Wed, 23 Oct 2019 18:58:58 +0200
Subject: [PATCH 014/168] fix: add translation
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4525 -> 4564 bytes
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index 275d056f743de4a049cddd0a73d894ef3df7d4fc..2c14a3dac91cb753c477b6e19c5b2be0216e5564 100644
GIT binary patch
delta 1128
zcmXxjPe>GD7{~Evo5^){HPbC~-OAb3+S0OG`?pXL5n*O|3p(_VAUYT#WJ02Q=}-oN
z3_;StGKc~r#OTl;DM_d<5k;4vE*-3cECtae==j_yeiBMOZ}DtPJ^Bt3V~32OgZuC~He-}>C~yu{@^(}L
zM=^!nsES-dReY?H`fI@h9;h_aID;Q?8;(!@I-52rA?2p*NB9WOq@AWfB!&7HMTE*#(#j?l}#SeDK;y
zB{wox3?;UOTZyP$t-&8ByMHqyMdUD*E`7El@&%lF>2bt
delta 1091
zcmXxiPe@cz6vy#1&9iaJX&n1A`KO#5YckVj@=rt1rZCV&g9I)FkrZ4M3PBr#W)NJ2
zuJYwVh$tdgnUE3vSp+U?(L#q5DuO^FThvYyr0?(Xe9W8Az3;ty?z!jQ<>Xwl@U6m`
zHm)pp4fmITSq>ir`Qut~%)+>e2k{3s;3gizWQkclp2xk|kNWNgmg5-K;uI?W3)Ebn
zFkx1((%okJdC-6wcmX4L72|l#cN7oMzlR~5MNRM=-{N~bfwy^CfsauMzC?}th==hz
zD()ukVSbC1nnig~gFLg-cnn)nANHU!AHf7pVk167CAxxIz#8hif0)2HWm5uK-yAC5
z5bFCpRx!U#(KwECsH!jFKKzVS+18M1**daF`-@6+!|$^UuOC9KJc=4$hdlwa460Ih
z*&kJS2Agmm3rA>trO}96$j?$`W_sR&G3>?^4q_!v;$?h_r|>r_aGY|e)J>=aE@Cxy
zqbf0ss`R-3{(gk|t0Yf&u!!&Q9OfCVQoce3TtZFo6Saris6^sKQ-T@P1eZ{i=|yef
zfbTdq)1SpUETZPwh*Ez|kfuHg*nvl}9~B^vO7IaXq1RZ2MN}exP~*a6p~II%ZDALZ
zqg_F5T_0-e27Rxi61|!B8>7g%u`$%)m_P-1h-{&k|I*FHb={1|GczMVjK6)rBm7L-f?o_G%X#8vkOJ%
Pm$UF8d@JO3#U}m(b3$RG
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index 8fcedfc5..e2624262 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -150,7 +150,7 @@ msgstr "Association à {what}"
#, python-brace-format
msgid "Yo {what}!"
-msgstr ""
+msgstr "Yo {what} !"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
From ea14cb370ee11c23f8918c166b1bac62985036e8 Mon Sep 17 00:00:00 2001
From: Xavier Stouder
Date: Wed, 23 Oct 2019 19:04:28 +0200
Subject: [PATCH 015/168] fix: forgot some exclamation mark fix
Signed-off-by: Xavier Stouder
---
pwnagotchi/locale/fr/LC_MESSAGES/voice.mo | Bin 4564 -> 4568 bytes
pwnagotchi/locale/fr/LC_MESSAGES/voice.po | 8 ++++----
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo b/pwnagotchi/locale/fr/LC_MESSAGES/voice.mo
index 2c14a3dac91cb753c477b6e19c5b2be0216e5564..d45922a50ff38f34742b0801674fcd2240d156c7 100644
GIT binary patch
delta 432
zcmXZYy-Pw-7{~E*74w2G_m;1vh-suCI3?~54Gs1uM6^a*prBmGXlN0q?vjWSI4I)K
zWJ-&(LH-$nwflYYPM`CfbDrn?IN#ZIc68>lj+9dOex-6~;V};I66aVzJ)l$sFEET(
z7{nV)-~&eS84Y}M&c_&|{=@~Yv5c=YCvg>2*8ji_8#2H{cN~)_K}vXt*C^+==*1eI
zV#~3MMd~li;1=bfsJRbZMVWVpDQu$LvrTKCaK?rt`oRD!TCz
zC60)KoygkSL8L4h&t`7zqJi0mB2ZFtCu+E)2xYxX6TYAUG-M
zNF-w70~m<$DF}#v9Dx6zlkoml#hfl|~Pb
zv4Ix0v4CC7;0RMViQZo@OTEMo{KhJdX+Fg-O9lS}w_L~qo$eFNp#+(Mbx!6FV&`p0Pq^Sh60I?hJCqaUC{mjr|K=d
diff --git a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
index e2624262..ecbc04ca 100644
--- a/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/fr/LC_MESSAGES/voice.po
@@ -22,13 +22,13 @@ msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
-msgstr "Bonjour, je suis Pwnagotchi! Démarrage..."
+msgstr "Bonjour, je suis Pwnagotchi ! Démarrage..."
msgid "New day, new hunt, new pwns!"
msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !"
msgid "Hack the Planet!"
-msgstr "Hack la planète!"
+msgstr "Hack la planète !"
msgid "AI ready."
msgstr "L'IA est prête."
@@ -47,10 +47,10 @@ msgid "I'm bored ..."
msgstr "Je m'ennuie..."
msgid "Let's go for a walk!"
-msgstr "Allons faire un tour!"
+msgstr "Allons faire un tour !"
msgid "This is the best day of my life!"
-msgstr "C'est le meilleur jour de ma vie!"
+msgstr "C'est le meilleur jour de ma vie !"
msgid "Shitty day :/"
msgstr "Journée de merde :/"
From eda8a18788647c9e1bdb467b38bd273bf85e3afc Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:08:43 +0200
Subject: [PATCH 016/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/mesh/peer.py | 2 +-
pwnagotchi/mesh/utils.py | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py
index a3ddd553..61a44132 100644
--- a/pwnagotchi/mesh/peer.py
+++ b/pwnagotchi/mesh/peer.py
@@ -12,7 +12,7 @@ def parse_rfc3339(dt):
class Peer(object):
def __init__(self, obj):
now = time.time()
- just_met = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
+ just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
self.first_met = parse_rfc3339(obj.get('met_at', just_met))
self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 9a67f38f..a8f652f3 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -100,5 +100,6 @@ class AsyncAdvertiser(object):
except Exception as e:
logging.warning("error while polling pwngrid-peer: %s" % e)
+ logging.debug(e, exc_info=True)
time.sleep(1)
From 3ab2088c79a20c83a3178061e05bd9c09cf925d5 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:09:36 +0200
Subject: [PATCH 017/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/mesh/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index a8f652f3..bb77eb5d 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -86,7 +86,7 @@ class AsyncAdvertiser(object):
to_delete.append(ident)
for ident in to_delete:
- self._on_lost_peer(peer)
+ self._on_lost_peer(self._peers[ident])
del self._peers[ident]
for ident, peer in new_peers.items():
From b85e4174dca7d837362bcf710a51984efe0bc079 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:21:42 +0200
Subject: [PATCH 018/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/mesh/peer.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py
index 61a44132..c82058ed 100644
--- a/pwnagotchi/mesh/peer.py
+++ b/pwnagotchi/mesh/peer.py
@@ -13,9 +13,18 @@ class Peer(object):
def __init__(self, obj):
now = time.time()
just_met = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
- self.first_met = parse_rfc3339(obj.get('met_at', just_met))
- self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
- self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
+
+ try:
+ self.first_met = parse_rfc3339(obj.get('met_at', just_met))
+ self.first_seen = parse_rfc3339(obj.get('detected_at', just_met))
+ self.prev_seen = parse_rfc3339(obj.get('prev_seen_at', just_met))
+ except Exception as e:
+ logging.warning("error while parsing peer timestamps: %s" % e)
+ logging.debug(e, exc_info=True)
+ self.first_met = just_met
+ self.first_seen = just_met
+ self.prev_seen = just_met
+
self.last_seen = now # should be seen_at
self.encounters = obj.get('encounters', 0)
self.session_id = obj.get('session_id', '')
@@ -75,4 +84,4 @@ class Peer(object):
return '%s@%s' % (self.name(), self.identity())
def is_closer(self, other):
- return self.rssi > other.rssi
\ No newline at end of file
+ return self.rssi > other.rssi
From d20f6c8a52c598b13c035f26a187c542f23487fa Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:28:05 +0200
Subject: [PATCH 019/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/plugins/default/grid.py | 2 +-
pwnagotchi/utils.py | 5 ++++-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index 5b32635c..d111f9ab 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -135,4 +135,4 @@ def on_internet_available(agent):
logging.debug("grid: reporting disabled")
except Exception as e:
- logging.error("grid api: %s" % e)
+ logging.error("grid api: %s" % e)
\ No newline at end of file
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index ab7acfd3..8532ae44 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -108,7 +108,10 @@ def setup_logging(args, config):
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
-
+ # https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1
+ requests_log = logging.getLogger("requests")
+ requests_log.addHandler(logging.NullHandler())
+ requests_log.propagate = False
def secs_to_hhmmss(secs):
mins, secs = divmod(secs, 60)
From 68d7686a03d33be255bbe21bb0318a4341e62b9f Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:32:24 +0200
Subject: [PATCH 020/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/agent.py | 3 ++-
pwnagotchi/automata.py | 2 ++
pwnagotchi/bettercap.py | 33 +++++++++++++++++----------------
pwnagotchi/mesh/utils.py | 4 ++--
4 files changed, 23 insertions(+), 19 deletions(-)
diff --git a/pwnagotchi/agent.py b/pwnagotchi/agent.py
index b3dd8cf4..53aa803c 100644
--- a/pwnagotchi/agent.py
+++ b/pwnagotchi/agent.py
@@ -284,12 +284,13 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
self.run('events.clear')
- logging.debug("event polling started ...")
while True:
time.sleep(1)
new_shakes = 0
+ logging.debug("polling events ...")
+
try:
s = self.session()
self._update_uptime(s)
diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py
index b0ae92fd..2d35392a 100644
--- a/pwnagotchi/automata.py
+++ b/pwnagotchi/automata.py
@@ -89,6 +89,8 @@ class Automata(object):
return self._epoch.any_activity
def next_epoch(self):
+ logging.debug("agent.next_epoch()")
+
was_stale = self.is_stale()
did_miss = self._epoch.num_missed
diff --git a/pwnagotchi/bettercap.py b/pwnagotchi/bettercap.py
index d55fc334..05213aee 100644
--- a/pwnagotchi/bettercap.py
+++ b/pwnagotchi/bettercap.py
@@ -3,6 +3,20 @@ import requests
from requests.auth import HTTPBasicAuth
+def decode(r, verbose_errors=True):
+ try:
+ return r.json()
+ except Exception as e:
+ if r.status_code == 200:
+ logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
+ else:
+ err = "error %d: %s" % (r.status_code, r.text.strip())
+ if verbose_errors:
+ logging.info(err)
+ raise Exception(err)
+ return r.text
+
+
class Client(object):
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
self.hostname = hostname
@@ -13,27 +27,14 @@ class Client(object):
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
self.auth = HTTPBasicAuth(username, password)
- def _decode(self, r, verbose_errors=True):
- try:
- return r.json()
- except Exception as e:
- if r.status_code == 200:
- logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
- else:
- err = "error %d: %s" % (r.status_code, r.text.strip())
- if verbose_errors:
- logging.info(err)
- raise Exception(err)
- return r.text
-
def session(self):
r = requests.get("%s/session" % self.url, auth=self.auth)
- return self._decode(r)
+ return decode(r)
def events(self):
r = requests.get("%s/events" % self.url, auth=self.auth)
- return self._decode(r)
+ return decode(r)
def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})
- return self._decode(r, verbose_errors=verbose_errors)
+ return decode(r, verbose_errors=verbose_errors)
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index bb77eb5d..7b5c92d1 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -66,9 +66,9 @@ class AsyncAdvertiser(object):
def _adv_poller(self):
while True:
- logging.debug("polling pwngrid-peer for peers ...")
-
try:
+ logging.debug("polling pwngrid-peer for peers ...")
+
grid_peers = grid.peers()
new_peers = {}
From 885fddfce8a99c4be996b0152c3f7876be721251 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:35:51 +0200
Subject: [PATCH 021/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/utils.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pwnagotchi/utils.py b/pwnagotchi/utils.py
index 8532ae44..4f3d10da 100644
--- a/pwnagotchi/utils.py
+++ b/pwnagotchi/utils.py
@@ -108,7 +108,9 @@ def setup_logging(args, config):
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
+
# https://stackoverflow.com/questions/24344045/how-can-i-completely-remove-any-logging-from-requests-module-in-python?noredirect=1&lq=1
+ logging.getLogger("urllib3").propagate = False
requests_log = logging.getLogger("requests")
requests_log.addHandler(logging.NullHandler())
requests_log.propagate = False
From d16180189e6f3629d40568c5ba9afad7c6a18850 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:41:14 +0200
Subject: [PATCH 022/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/mesh/peer.py | 12 ++++++------
pwnagotchi/mesh/utils.py | 4 +++-
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/pwnagotchi/mesh/peer.py b/pwnagotchi/mesh/peer.py
index c82058ed..6415752c 100644
--- a/pwnagotchi/mesh/peer.py
+++ b/pwnagotchi/mesh/peer.py
@@ -53,20 +53,20 @@ class Peer(object):
def first_encounter(self):
return self.encounters == 1
- def days_since_first_met(self):
- return (datetime.datetime.now() - self.first_met).days
-
def face(self):
return self.adv.get('face', faces.FRIEND)
def name(self):
- return self.adv.get('name')
+ return self.adv.get('name', '???')
def identity(self):
- return self.adv.get('identity')
+ return self.adv.get('identity', '???')
+
+ def full_name(self):
+ return "%s@%s" % (self.name(), self.identity())
def version(self):
- return self.adv.get('version')
+ return self.adv.get('version', '1.0.0a')
def pwnd_run(self):
return int(self.adv.get('pwnd_run', 0))
diff --git a/pwnagotchi/mesh/utils.py b/pwnagotchi/mesh/utils.py
index 7b5c92d1..a3c9bdad 100644
--- a/pwnagotchi/mesh/utils.py
+++ b/pwnagotchi/mesh/utils.py
@@ -57,10 +57,12 @@ class AsyncAdvertiser(object):
return sum(peer.encounters for _, peer in self._peers.items())
def _on_new_peer(self, peer):
+ logging.info("new peer %s detected (%d encounters)" % (peer.full_name(), peer.encounters))
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
def _on_lost_peer(self, peer):
+ logging.info("lost peer %s" % peer.full_name())
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
@@ -102,4 +104,4 @@ class AsyncAdvertiser(object):
logging.warning("error while polling pwngrid-peer: %s" % e)
logging.debug(e, exc_info=True)
- time.sleep(1)
+ time.sleep(3)
From 84616827ad0b38442fa69af9163e799dd6a68114 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:46:26 +0200
Subject: [PATCH 023/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/plugins/__init__.py | 4 +++-
pwnagotchi/voice.py | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py
index 6dd7b648..b2219747 100644
--- a/pwnagotchi/plugins/__init__.py
+++ b/pwnagotchi/plugins/__init__.py
@@ -21,6 +21,7 @@ def on(event_name, *args, **kwargs):
plugin.__dict__[cb_name](*args, **kwargs)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
+ logging.error(e, exc_info=True)
def load_from_file(filename):
@@ -47,7 +48,8 @@ def load_from_path(path, enabled=()):
def load(config):
- enabled = [name for name, options in config['main']['plugins'].items() if 'enabled' in options and options['enabled']]
+ enabled = [name for name, options in config['main']['plugins'].items() if
+ 'enabled' in options and options['enabled']]
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
# load default plugins
loaded = load_from_path(default_path, enabled=enabled)
diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py
index cf98fdad..b718e4e2 100644
--- a/pwnagotchi/voice.py
+++ b/pwnagotchi/voice.py
@@ -136,7 +136,7 @@ class Voice:
def on_unread_messages(self, count, total):
s = 's' if count > 1 else ''
- return self._('You have {count} new message{plural}!').format(num=count, plural=s)
+ return self._('You have {count} new message{plural}!').format(count=count, plural=s)
def on_rebooting(self):
return self._("Ops, something went wrong ... Rebooting ...")
From d636c2b1f2b21ae70d1a12141c7c137525b47e81 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 19:53:23 +0200
Subject: [PATCH 024/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/plugins/default/grid.py | 3 ++-
pwnagotchi/voice.py | 9 ++++++---
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index d111f9ab..4c411b44 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -67,6 +67,7 @@ def is_excluded(what):
def on_ui_update(ui):
if UNREAD_MESSAGES > 0:
+ logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
ui.on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
@@ -135,4 +136,4 @@ def on_internet_available(agent):
logging.debug("grid: reporting disabled")
except Exception as e:
- logging.error("grid api: %s" % e)
\ No newline at end of file
+ logging.error("grid api: %s" % e)
diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py
index b718e4e2..249ac21b 100644
--- a/pwnagotchi/voice.py
+++ b/pwnagotchi/voice.py
@@ -70,9 +70,12 @@ class Voice:
self._('My crime is that of curiosity ...')])
def on_new_peer(self, peer):
- return random.choice([
- self._('Hello {name}! Nice to meet you.').format(name=peer.name()),
- self._('Unit {name} is nearby!').format(name=peer.name())])
+ if peer.first_encounter():
+ return random.choice([
+ self._('Hello {name}! Nice to meet you.').format(name=peer.name())])
+ else:
+ return random.choice([
+ self._('Unit {name} is nearby!').format(name=peer.name())])
def on_lost_peer(self, peer):
return random.choice([
From 9a72a8868bd4c282425db553ef7c7510e60c41d2 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Wed, 23 Oct 2019 20:02:42 +0200
Subject: [PATCH 025/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/automata.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py
index 2d35392a..556af636 100644
--- a/pwnagotchi/automata.py
+++ b/pwnagotchi/automata.py
@@ -109,7 +109,7 @@ class Automata(object):
# after X times being active, the status is set to happy / excited
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
self.set_excited()
- elif self._has_support_network_for(1.0):
+ elif self._epoch.active_for >= 5 and self._has_support_network_for(1.0):
self.set_grateful()
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
From 04b2ee5b444d56b259c7c12ac45c7d9838d7fbb4 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Wed, 23 Oct 2019 20:57:26 +0200
Subject: [PATCH 026/168] fix en and de
---
pwnagotchi/locale/de/LC_MESSAGES/voice.po | 19 ++++++++++++++++---
pwnagotchi/locale/voice.pot | 8 +++++++-
2 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.po b/pwnagotchi/locale/de/LC_MESSAGES/voice.po
index 8b96f335..7fdb0c00 100644
--- a/pwnagotchi/locale/de/LC_MESSAGES/voice.po
+++ b/pwnagotchi/locale/de/LC_MESSAGES/voice.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-09 17:42+0200\n"
+"POT-Creation-Date: 2019-10-23 20:56+0200\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"
@@ -34,6 +34,9 @@ msgstr "KI bereit."
msgid "The neural network is ready."
msgstr "Das neurale Netz ist bereit."
+msgid "Generating keys, do not turn off ..."
+msgstr ""
+
#, 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."
@@ -75,11 +78,11 @@ msgid "My crime is that of curiosity ..."
msgstr "Mein Verbrechen ist das der Neugier ..."
#, python-brace-format
-msgid "Hello {name}! Nice to meet you. {name}"
+msgid "Hello {name}! Nice to meet you."
msgstr "Hallo {name}, nett Dich kennenzulernen."
#, python-brace-format
-msgid "Unit {name} is nearby! {name}"
+msgid "Unit {name} is nearby!"
msgstr "Gerät {name} ist in der nähe!!"
#, python-brace-format
@@ -101,6 +104,12 @@ msgstr "{name} verpasst!"
msgid "Missed!"
msgstr "Verpasst!"
+msgid "Good friends are a blessing!"
+msgstr ""
+
+msgid "I love my friends!"
+msgstr ""
+
msgid "Nobody wants to play with me ..."
msgstr "Niemand will mit mir spielen ..."
@@ -163,6 +172,10 @@ msgstr "Kicke {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
+#, python-brace-format
+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 ..."
diff --git a/pwnagotchi/locale/voice.pot b/pwnagotchi/locale/voice.pot
index 7d20973c..6d2a1da1 100644
--- a/pwnagotchi/locale/voice.pot
+++ b/pwnagotchi/locale/voice.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-10-23 18:37+0200\n"
+"POT-Creation-Date: 2019-10-23 20:56+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -105,6 +105,12 @@ msgstr ""
msgid "Missed!"
msgstr ""
+msgid "Good friends are a blessing!"
+msgstr ""
+
+msgid "I love my friends!"
+msgstr ""
+
msgid "Nobody wants to play with me ..."
msgstr ""
From f39c154b2318e14408ee89031336cddf01aa9d00 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Wed, 23 Oct 2019 20:59:22 +0200
Subject: [PATCH 027/168] fix en and de
---
pwnagotchi/locale/de/LC_MESSAGES/voice.mo | Bin 4204 -> 4547 bytes
pwnagotchi/locale/de/LC_MESSAGES/voice.po | 6 +++---
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/pwnagotchi/locale/de/LC_MESSAGES/voice.mo b/pwnagotchi/locale/de/LC_MESSAGES/voice.mo
index 8944e859db04407d31442b9cb1e369c182ff987f..683dca252c8c344d352b7143eb61c867eda6786e 100644
GIT binary patch
delta 1533
zcmYk+U2Ke59LMo9+HPNM>%A6rM!k)fQR=N$Qnd*|yrn@ZZXTV}b{NmYJTquSCY#;3
z5-uiNT!{!*B37?d!UcCth+ev2B^x1O$&zp(`2NOIangT3=Q;D7xBodauk5|%!jtjk
ze;G;}F`f7~X3TcHR>nWdi*jSC@g+{ecbLHH3S%Z?E6&EvsQv-g;%U_T=dl5=<4nAZ
zdhaQ&Hl|=cQL!|v_{o?FxEsgg0n`8|aV!>b3SNr5g%f#xgq8RlHPL@KgkNwj4)LMJ
zdxl!@dsM&LDq|X%-&iUdxD9J?C)VRW)W8{xV-EG+0BQ%<(c&;J#8;?=Hj*xFWG?Ex
zHE3}UY5~V1&*Bv3H&>~s!yWtuM^FoSjdSoLl6BKWBgrz&$mUD}wZN9>d3E&Mj@t1?
z{2BM+uQ6lN$e+2w`DnfySXfNuJ{9fkf7FCEb>CLrf;5?}I2F54DGqTS{)w~j8m`5M
zsFc@{2JO5R8*v9}{50xFe@BgXZY=p%6aqG43(pQ1Xv!4a%v8=CMjGKU$%OX^pT
zN@)T$(K=MWE*ytPP!s-vB+*EX4e8|pi;gH7vp-=(H%w|-LXQ{Ak}ax@(gmH%m8Y@i^$mKCUTUe
z$;aR_LM2IP=RcMuJhVj(T8tJ>_tG6wv*s&k676gSq0}ab7DDH!vmGtEm`bIJcA&DH
zP+GM^l~n|ZEG<~ie80^MqKQy8^^>xISV=^sP&$vL(bIg4M|GuLXF82g>PAa56&;a^
z?#yV>Mb&Lz7&Yj}Mj6vLN0)jLVF}Z*$!49G>Q9=EFf0y~?^@@4m-TXKztg=KZ
z(f8tm4=NI6#m`lp&0Rs>_IZ4IqC3d@xs%p+$Lt~cd%f<&;Y>d3Wd^rZzm0WhEosO2
zTS`mu)7^bJ>*d3+yU)wyod0bx`JA&GvM%qZoMj0q>(ajMbiK}x53a4PicQR<-F}ur
W^Y<6ygY9*X%ZeXrlEtYFonHal$G1oT
delta 1227
zcmY+^Pe@cz6vy#1%^!1|ank-wR%6d5CvBRvOcJzm5oVic5F+}s5UE8qBGMv^cw3`v
z(ak`u=9FL6mJ2E97PZB
z<9ajSo^zvx-(Vb9Fp1xs4H2^q%+pwheW(ov@fqeZgE78r#vatchERWZ9yjA8YTa8H
z#Rr&Re|y9YADidWh6|`4enOqx*49p(!YtFnsDoZYGO%l?-`z$JOQ-|9#uP52*879{
zeUfr+!7N7E--fuMYL-Wmwo^z_b`Ev0F*m>L=98#Qr?3%cu%EWgqB2|LqK!Ua4u7H!
z&`zJ!szY2_@HqM^
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
From 64aad56fbaeb0fe316772319801f25f309019c45 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 14:47:51 +0100
Subject: [PATCH 148/168] =?UTF-8?q?releasing=20v=1B[3~1.1.1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pwnagotchi/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py
index 3c946b64..bc453f13 100644
--- a/pwnagotchi/__init__.py
+++ b/pwnagotchi/__init__.py
@@ -6,7 +6,7 @@ import re
import pwnagotchi.ui.view as view
import pwnagotchi
-version = '1.1.0'
+version = '[3~1.1.1'
_name = None
From 03067da00542d87c80a3df6c2f6ec3aa2705caf6 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 14:49:44 +0100
Subject: [PATCH 149/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py
index bc453f13..3c946b64 100644
--- a/pwnagotchi/__init__.py
+++ b/pwnagotchi/__init__.py
@@ -6,7 +6,7 @@ import re
import pwnagotchi.ui.view as view
import pwnagotchi
-version = '[3~1.1.1'
+version = '1.1.0'
_name = None
From 3c32bbb58216f757805bc351942b512d596aa391 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 14:50:03 +0100
Subject: [PATCH 150/168] releasing v1.1.1
---
pwnagotchi/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py
index 3c946b64..5474c073 100644
--- a/pwnagotchi/__init__.py
+++ b/pwnagotchi/__init__.py
@@ -6,7 +6,7 @@ import re
import pwnagotchi.ui.view as view
import pwnagotchi
-version = '1.1.0'
+version = '1.1.1'
_name = None
From 31d401e03b7773e0c806140622436662662dd500 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 15:39:59 +0100
Subject: [PATCH 151/168] fix: increased delay before shutting down to allow
slower displays to update (closes #446)
---
pwnagotchi/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/__init__.py b/pwnagotchi/__init__.py
index 5474c073..c0cac857 100644
--- a/pwnagotchi/__init__.py
+++ b/pwnagotchi/__init__.py
@@ -103,7 +103,7 @@ def shutdown():
if view.ROOT:
view.ROOT.on_shutdown()
# give it some time to refresh the ui
- time.sleep(5)
+ time.sleep(10)
os.system("sync")
os.system("halt")
From bd63f71a1d6e00968037a4200c19fbc757998d6f Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 17:25:21 +0100
Subject: [PATCH 152/168] fix: +x to /usr/bin/* while creating the .img
---
builder/data/usr/bin/pwnlib | 0
builder/pwnagotchi.json | 6 ++++++
2 files changed, 6 insertions(+)
mode change 100644 => 100755 builder/data/usr/bin/pwnlib
diff --git a/builder/data/usr/bin/pwnlib b/builder/data/usr/bin/pwnlib
old mode 100644
new mode 100755
diff --git a/builder/pwnagotchi.json b/builder/pwnagotchi.json
index c86468f8..2591eacd 100644
--- a/builder/pwnagotchi.json
+++ b/builder/pwnagotchi.json
@@ -89,6 +89,12 @@
"source": "data/etc/systemd/system/bettercap.service",
"destination": "/etc/systemd/system/bettercap.service"
},
+ {
+ "type": "shell",
+ "inline": [
+ "chmod +x /usr/bin/*"
+ ]
+ },
{
"type": "ansible-local",
"playbook_file": "pwnagotchi.yml",
From 8118a10a6a9d08bfec1a3d1d0c7d411fe69ac226 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Thu, 31 Oct 2019 18:24:43 +0100
Subject: [PATCH 153/168] new: auto-update is now enabled by default
---
pwnagotchi/defaults.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 72a75205..bb78d8ad 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -21,9 +21,9 @@ main:
- YourHomeNetworkHere
auto-update:
- enabled: false
- interval: 12 # every 12 hours
+ enabled: true
install: true # if false, it will only warn that updates are available, if true it will install them
+ interval: 1 # every 1 hour
auto-backup:
enabled: false
From 3efa96b292dda53a0b9eba4fbed7e0e63db97cef Mon Sep 17 00:00:00 2001
From: Jeremy O'Brien
Date: Thu, 31 Oct 2019 13:18:42 -0400
Subject: [PATCH 154/168] enhancement: Improve the backup script
- Significantly decrease time it takes to save a backup
- Remove host dependency on 'zip' binary
- Preserve file attributes on backed-up files
- Avoid copying files on the pi itself to /tmp
Signed-off-by: Jeremy O'Brien
---
scripts/backup.sh | 32 ++------------------------------
scripts/restore.sh | 16 ++++++++++++++++
2 files changed, 18 insertions(+), 30 deletions(-)
create mode 100755 scripts/restore.sh
diff --git a/scripts/backup.sh b/scripts/backup.sh
index 281a2bb4..6fa17849 100755
--- a/scripts/backup.sh
+++ b/scripts/backup.sh
@@ -3,7 +3,7 @@
# name of the ethernet gadget interface on the host
UNIT_HOSTNAME=${1:-10.0.0.2}
# output backup zip file
-OUTPUT=${2:-pwnagotchi-backup.zip}
+OUTPUT=${2:-pwnagotchi-backup.tgz}
# username to use for ssh
USERNAME=${3:-pi}
# what to backup
@@ -19,38 +19,10 @@ FILES_TO_BACKUP=(
/home/pi/.bashrc
)
-if ! type "zip" >/dev/null 2>&1; then
- echo "This script requires zip, please resolve and try again"
- exit 1
-fi
-
ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || {
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
exit 1
}
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
-
-ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null
-
-for file in "${FILES_TO_BACKUP[@]}"; do
- dir=$(dirname "$file")
-
- echo "@ copying $file to /tmp/backup$dir"
-
- ssh "${USERNAME}@${UNIT_HOSTNAME}" "mkdir -p /tmp/backup${dir}" > /dev/null
- ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo cp -r ${file} /tmp/backup${dir}" > /dev/null
-done
-
-ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo chown ${USERNAME}:${USERNAME} -R /tmp/backup" > /dev/null
-
-echo "@ pulling from $UNIT_HOSTNAME ..."
-
-rm -rf /tmp/backup
-scp -rC "${USERNAME}@${UNIT_HOSTNAME}":/tmp/backup /tmp/
-
-echo "@ compressing ..."
-
-zip -r -9 -q "$OUTPUT" /tmp/backup
-rm -rf /tmp/backup
-
+ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar cv ${FILES_TO_BACKUP[@]}" | gzip -9 > "$OUTPUT"
diff --git a/scripts/restore.sh b/scripts/restore.sh
new file mode 100755
index 00000000..ca60a80a
--- /dev/null
+++ b/scripts/restore.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+# name of the ethernet gadget interface on the host
+UNIT_HOSTNAME=${1:-10.0.0.2}
+# output backup zip file
+BACKUP=${2:-pwnagotchi-backup.tgz}
+# username to use for ssh
+USERNAME=${3:-pi}
+
+ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || {
+ echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
+ exit 1
+}
+
+echo "@ restoring $BACKUP to $UNIT_HOSTNAME ..."
+cat ${BACKUP} | ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar xzv -C /"
From bfdaffa14bc383ed2d36967db58f9fbe5979113b Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 09:20:06 +0100
Subject: [PATCH 155/168] Add reference to network object
---
pwnagotchi/plugins/default/bt-tether.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 509519cd..2da90cf8 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -18,7 +18,7 @@ from pwnagotchi.utils import StatusFile
READY = False
INTERVAL = StatusFile('/root/.bt-tether')
OPTIONS = dict()
-
+NETWORK = None
class BTError(Exception):
"""
@@ -275,15 +275,15 @@ class BTNap:
try:
logging.debug('BT-TETHER: Connecting to nap network ...')
net.Connect('nap')
- return True
+ return net, True
except dbus.exceptions.DBusException as err:
if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected':
- return True
+ return net, True
connected = BTNap.prop_get(net, 'Connected')
if not connected:
- return False
- return True
+ return None, False
+ return net, True
class SystemdUnitWrapper:
@@ -444,6 +444,7 @@ def on_ui_update(ui):
if READY:
global INTERVAL
+ global NETWORK
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return
@@ -488,7 +489,9 @@ def on_ui_update(ui):
if not btnap_iface.exists():
# connected and paired but not napping
logging.debug('BT-TETHER: Try to connect to nap ...')
- if BTNap.nap(dev_remote):
+ network, status = BTNap.nap(dev_remote)
+ NETWORK = network
+ if status:
logging.info('BT-TETHER: Napping!')
ui.set('bluetooth', 'C')
time.sleep(5)
From 1827ee564c017c549bee0c482868d3d8dcef115f Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 10:04:36 +0100
Subject: [PATCH 156/168] Add version option
---
bin/pwnagotchi | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/bin/pwnagotchi b/bin/pwnagotchi
index 82e47122..e1409245 100755
--- a/bin/pwnagotchi
+++ b/bin/pwnagotchi
@@ -31,7 +31,15 @@ if __name__ == '__main__':
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
help="Enable debug logs.")
+ parser.add_argument('--version', dest="version", action="store_true", default=False,
+ help="Prints the version.")
+
args = parser.parse_args()
+
+ if args.version:
+ print(pwnagotchi.version)
+ SystemExit(0)
+
config = utils.load_config(args)
utils.setup_logging(args, config)
From ae330dc0b5219100f5830194274644d511a57716 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Fri, 1 Nov 2019 12:12:37 +0100
Subject: [PATCH 157/168] fix: don't reset network interfaces configuration if
not needed (closes #483)
---
setup.py | 42 ++++++++++++++++++++++++++++--------------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/setup.py b/setup.py
index fe58a736..906dcfdf 100644
--- a/setup.py
+++ b/setup.py
@@ -5,25 +5,39 @@ import os
import glob
import shutil
-setup_path = os.path.dirname(__file__)
-data_path = os.path.join(setup_path, "builder/data")
-for source_filename in glob.glob("%s/**" % data_path, recursive=True):
- if os.path.isfile(source_filename):
- dest_filename = source_filename.replace(data_path, '')
+def install_file(source_filename, dest_filename):
+ # do not overwrite network configuration if it exists already
+ # https://github.com/evilsocket/pwnagotchi/issues/483
+ if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename):
+ print("%s exists, skipping ..." % dest_filename)
+ return
+
+ print("installing %s to %s ..." % (source_filename, dest_filename))
+ try:
dest_folder = os.path.dirname(dest_filename)
+ if not os.path.isdir(dest_folder):
+ os.makedirs(dest_folder)
- print("installing %s to %s ..." % (source_filename, dest_filename))
- try:
- if not os.path.isdir(dest_folder):
- os.makedirs(dest_folder)
+ shutil.copyfile(source_filename, dest_filename)
+ except Exception as e:
+ print("error installing %s: %s" % (source_filename, e))
- shutil.copyfile(source_filename, dest_filename)
- except Exception as e:
- print("error installing %s: %s" % (source_filename, e))
-# reload systemd units
-os.system("systemctl daemon-reload")
+def install_system_files():
+ setup_path = os.path.dirname(__file__)
+ data_path = os.path.join(setup_path, "builder/data")
+
+ for source_filename in glob.glob("%s/**" % data_path, recursive=True):
+ if os.path.isfile(source_filename):
+ dest_filename = source_filename.replace(data_path, '')
+ install_file(source_filename, dest_filename)
+
+ # reload systemd units
+ os.system("systemctl daemon-reload")
+
+
+install_system_files()
required = []
with open('requirements.txt') as fp:
From 2f948306ebeabf10050bb0c07a3254b420c30751 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Fri, 1 Nov 2019 13:51:45 +0100
Subject: [PATCH 158/168] misc: refactored plugin system to use classes
---
pwnagotchi/plugins/__init__.py | 64 +++--
pwnagotchi/plugins/default/AircrackOnly.py | 81 +++---
pwnagotchi/plugins/default/auto-backup.py | 74 +++--
pwnagotchi/plugins/default/auto-update.py | 57 ++--
pwnagotchi/plugins/default/bt-tether.py | 132 ++++-----
pwnagotchi/plugins/default/example.py | 258 ++++++++----------
pwnagotchi/plugins/default/gpio_buttons.py | 64 ++---
pwnagotchi/plugins/default/gps.py | 65 +++--
pwnagotchi/plugins/default/grid.py | 171 ++++++------
pwnagotchi/plugins/default/memtemp.py | 60 ++--
pwnagotchi/plugins/default/net-pos.py | 222 ++++++++-------
pwnagotchi/plugins/default/onlinehashcrack.py | 138 +++++-----
pwnagotchi/plugins/default/paw-gps.py | 30 +-
pwnagotchi/plugins/default/quickdic.py | 77 +++---
pwnagotchi/plugins/default/screen_refresh.py | 37 ++-
pwnagotchi/plugins/default/twitter.py | 77 +++---
.../plugins/default/unfiltered_example.py | 22 --
pwnagotchi/plugins/default/ups_lite.py | 32 +--
pwnagotchi/plugins/default/wigle.py | 192 +++++++------
pwnagotchi/plugins/default/wpa-sec.py | 139 +++++-----
20 files changed, 943 insertions(+), 1049 deletions(-)
delete mode 100644 pwnagotchi/plugins/default/unfiltered_example.py
diff --git a/pwnagotchi/plugins/__init__.py b/pwnagotchi/plugins/__init__.py
index cb58323d..a183294d 100644
--- a/pwnagotchi/plugins/__init__.py
+++ b/pwnagotchi/plugins/__init__.py
@@ -7,20 +7,20 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "defaul
loaded = {}
-def dummy_callback():
- pass
+class Plugin:
+ @classmethod
+ def __init_subclass__(cls, **kwargs):
+ super().__init_subclass__(**kwargs)
+ global loaded
+ 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
def on(event_name, *args, **kwargs):
- global loaded
- cb_name = 'on_%s' % event_name
for plugin_name, plugin in loaded.items():
- if cb_name in plugin.__dict__:
- try:
- plugin.__dict__[cb_name](*args, **kwargs)
- except Exception as e:
- logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
- logging.error(e, exc_info=True)
+ one(plugin_name, event_name, *args, **kwargs)
def one(plugin_name, event_name, *args, **kwargs):
@@ -28,15 +28,17 @@ def one(plugin_name, event_name, *args, **kwargs):
if plugin_name in loaded:
plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name
- if cb_name in plugin.__dict__:
+ callback = getattr(plugin, cb_name, None)
+ if callback is not None and callable(callback):
try:
- plugin.__dict__[cb_name](*args, **kwargs)
+ callback(*args, **kwargs)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
def load_from_file(filename):
+ logging.debug("loading %s" % filename)
plugin_name = os.path.basename(filename.replace(".py", ""))
spec = importlib.util.spec_from_file_location(plugin_name, filename)
instance = importlib.util.module_from_spec(spec)
@@ -46,19 +48,15 @@ def load_from_file(filename):
def load_from_path(path, enabled=()):
global loaded
+ logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")):
- try:
- name, plugin = load_from_file(filename)
- if name in loaded:
- raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
- elif name not in enabled:
- # print("plugin %s is not enabled" % name)
- pass
- else:
- loaded[name] = plugin
- except Exception as e:
- logging.warning("error while loading %s: %s" % (filename, e))
- logging.debug(e, exc_info=True)
+ plugin_name = os.path.basename(filename.replace(".py", ""))
+ if plugin_name in enabled:
+ try:
+ load_from_file(filename)
+ except Exception as e:
+ logging.warning("error while loading %s: %s" % (filename, e))
+ logging.debug(e, exc_info=True)
return loaded
@@ -66,17 +64,17 @@ def load_from_path(path, enabled=()):
def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']]
- custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
+
# load default plugins
- loaded = load_from_path(default_path, enabled=enabled)
- # set the options
- for name, plugin in loaded.items():
- plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
+ load_from_path(default_path, enabled=enabled)
+
# load custom ones
+ custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
if custom_path is not None:
- loaded = load_from_path(custom_path, enabled=enabled)
- # set the options
- for name, plugin in loaded.items():
- plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
+ load_from_path(custom_path, enabled=enabled)
+
+ # propagate options
+ for name, plugin in loaded.items():
+ plugin.options = config['main']['plugins'][name]
on('loaded')
diff --git a/pwnagotchi/plugins/default/AircrackOnly.py b/pwnagotchi/plugins/default/AircrackOnly.py
index b6d20286..4691e686 100644
--- a/pwnagotchi/plugins/default/AircrackOnly.py
+++ b/pwnagotchi/plugins/default/AircrackOnly.py
@@ -1,56 +1,57 @@
-__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
-__version__ = '1.0.1'
-__name__ = 'AircrackOnly'
-__license__ = 'GPL3'
-__description__ = 'confirm pcap contains handshake/PMKID or delete it'
-
-'''
-Aircrack-ng needed, to install:
-> apt-get install aircrack-ng
-'''
+import pwnagotchi.plugins as plugins
import logging
import subprocess
import string
import os
-OPTIONS = dict()
+'''
+Aircrack-ng needed, to install:
+> apt-get install aircrack-ng
+'''
-def on_loaded():
- logging.info("aircrackonly plugin loaded")
-def on_handshake(agent, filename, access_point, client_station):
- display = agent._view
- todelete = 0
+class AircrackOnly(plugins.Plugin):
+ __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
+ __version__ = '1.0.1'
+ __license__ = 'GPL3'
+ __description__ = 'confirm pcap contains handshake/PMKID or delete it'
- result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
- result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
- if result:
- logging.info("[AircrackOnly] contains handshake")
- else:
- todelete = 1
+ def __init__(self):
+ super().__init__(self)
+ self.text_to_set = ""
- if todelete == 0:
- result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
- result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
+ def on_loaded(self):
+ logging.info("aircrackonly plugin loaded")
+
+ def on_handshake(self, agent, filename, access_point, client_station):
+ display = agent._view
+ todelete = 0
+
+ result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
+ shell=True, stdout=subprocess.PIPE)
+ result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result:
- logging.info("[AircrackOnly] contains PMKID")
+ logging.info("[AircrackOnly] contains handshake")
else:
todelete = 1
- if todelete == 1:
- os.remove(filename)
- set_text("Removed an uncrackable pcap")
- display.update(force=True)
+ if todelete == 0:
+ result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
+ shell=True, stdout=subprocess.PIPE)
+ result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
+ if result:
+ logging.info("[AircrackOnly] contains PMKID")
+ else:
+ todelete = 1
-text_to_set = "";
-def set_text(text):
- global text_to_set
- text_to_set = text
+ if todelete == 1:
+ os.remove(filename)
+ self.text_to_set = "Removed an uncrackable pcap"
+ display.update(force=True)
-def on_ui_update(ui):
- global text_to_set
- if text_to_set:
- ui.set('face', "(>.<)")
- ui.set('status', text_to_set)
- text_to_set = ""
+ def on_ui_update(self, ui):
+ if self.text_to_set:
+ ui.set('face', "(>.<)")
+ ui.set('status', self.text_to_set)
+ self.text_to_set = ""
diff --git a/pwnagotchi/plugins/default/auto-backup.py b/pwnagotchi/plugins/default/auto-backup.py
index c235f787..9b15efc0 100644
--- a/pwnagotchi/plugins/default/auto-backup.py
+++ b/pwnagotchi/plugins/default/auto-backup.py
@@ -1,49 +1,47 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '1.0.0'
-__name__ = 'auto-backup'
-__license__ = 'GPL3'
-__description__ = 'This plugin backups files when internet is available.'
-
+import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
import logging
import os
import subprocess
-OPTIONS = dict()
-READY = False
-STATUS = StatusFile('/root/.auto-backup')
+class AutoBackup(plugins.Plugin):
+ __author__ = '33197631+dadav@users.noreply.github.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin backups files when internet is available.'
-def on_loaded():
- global READY
+ def __init__(self):
+ self.ready = False
+ self.status = StatusFile('/root/.auto-backup')
- if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None):
- logging.error("AUTO-BACKUP: No files to backup.")
- return
-
- if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
- logging.error("AUTO-BACKUP: Interval is not set.")
- return
-
- if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None):
- logging.error("AUTO-BACKUP: No commands given.")
- return
-
- READY = True
- logging.info("AUTO-BACKUP: Successfully loaded.")
-
-
-def on_internet_available(agent):
- global STATUS
-
- if READY:
- if STATUS.newer_then_days(OPTIONS['interval']):
+ def on_loaded(self):
+ if 'files' not in self.options or ('files' in self.options and self.options['files'] is None):
+ logging.error("AUTO-BACKUP: No files to backup.")
return
-
+
+ if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
+ logging.error("AUTO-BACKUP: Interval is not set.")
+ return
+
+ if 'commands' not in self.options or ('commands' in self.options and self.options['commands'] is None):
+ logging.error("AUTO-BACKUP: No commands given.")
+ return
+
+ self.ready = True
+ logging.info("AUTO-BACKUP: Successfully loaded.")
+
+ def on_internet_available(self, agent):
+ if not self.ready:
+ 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), OPTIONS['files']))
+ existing_files = list(filter(lambda f: os.path.exists(f), self.options['files']))
files_to_backup = " ".join(existing_files)
-
+
try:
display = agent.view()
@@ -51,10 +49,10 @@ def on_internet_available(agent):
display.set('status', 'Backing up ...')
display.update()
- for cmd in OPTIONS['commands']:
+ 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")
+ stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
raise OSError(f"Command failed (rc: {process.returncode})")
@@ -62,7 +60,7 @@ def on_internet_available(agent):
logging.info("AUTO-BACKUP: backup done")
display.set('status', 'Backup done!')
display.update()
- STATUS.update()
+ self.status.update()
except OSError as os_e:
logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup failed!')
diff --git a/pwnagotchi/plugins/default/auto-update.py b/pwnagotchi/plugins/default/auto-update.py
index 33654902..0a4b0ff0 100644
--- a/pwnagotchi/plugins/default/auto-update.py
+++ b/pwnagotchi/plugins/default/auto-update.py
@@ -1,9 +1,3 @@
-__author__ = 'evilsocket@gmail.com'
-__version__ = '1.1.1'
-__name__ = 'auto-update'
-__license__ = 'GPL3'
-__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
-
import os
import re
import logging
@@ -15,21 +9,9 @@ import glob
import pkg_resources
import pwnagotchi
+import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
-OPTIONS = dict()
-READY = False
-STATUS = StatusFile('/root/.auto-update')
-
-
-def on_loaded():
- global READY
- if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
- logging.error("[update] main.plugins.auto-update.interval is not set")
- return
- READY = True
- logging.info("[update] plugin loaded.")
-
def check(version, repo, native=True):
logging.debug("checking remote version for %s, local is %s" % (repo, version))
@@ -158,14 +140,32 @@ def parse_version(cmd):
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out))
-def on_internet_available(agent):
- global STATUS
+class AutoUpdate(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __version__ = '1.1.1'
+ __name__ = 'auto-update'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin checks when updates are available and applies them when internet is available.'
- logging.debug("[update] internet connectivity is available (ready %s)" % READY)
+ def __init__(self):
+ self.ready = False
+ self.status = StatusFile('/root/.auto-update')
- if READY:
- if STATUS.newer_then_hours(OPTIONS['interval']):
- logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval'])
+ 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):
+ 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 ...")
@@ -187,7 +187,8 @@ def on_internet_available(agent):
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']))
+ "update for %s available (local version is '%s'): %s" % (
+ repo, info['current'], info['url']))
info['service'] = svc_name
to_install.append(info)
@@ -195,7 +196,7 @@ def on_internet_available(agent):
num_installed = 0
if num_updates > 0:
- if OPTIONS['install']:
+ if self.options['install']:
for update in to_install:
if install(display, update):
num_installed += 1
@@ -204,7 +205,7 @@ def on_internet_available(agent):
logging.info("[update] done")
- STATUS.update()
+ self.status.update()
if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'})
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 2da90cf8..7c89387f 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -1,9 +1,3 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '1.0.0'
-__name__ = 'bt-tether'
-__license__ = 'GPL3'
-__description__ = 'This makes the display reachable over bluetooth'
-
import os
import time
import re
@@ -14,11 +8,8 @@ from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import StatusFile
+import pwnagotchi.plugins as plugins
-READY = False
-INTERVAL = StatusFile('/root/.bt-tether')
-OPTIONS = dict()
-NETWORK = None
class BTError(Exception):
"""
@@ -26,6 +17,7 @@ class BTError(Exception):
"""
pass
+
class BTNap:
"""
This class creates a bluetooth connection to the specified bt-mac
@@ -41,7 +33,6 @@ class BTNap:
def __init__(self, mac):
self._mac = mac
-
@staticmethod
def get_bus():
"""
@@ -59,9 +50,9 @@ class BTNap:
"""
manager = getattr(BTNap.get_manager, 'cached_obj', None)
if not manager:
- manager = BTNap.get_manager.cached_obj = dbus.Interface(
- BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
- 'org.freedesktop.DBus.ObjectManager' )
+ manager = BTNap.get_manager.cached_obj = dbus.Interface(
+ BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
+ 'org.freedesktop.DBus.ObjectManager')
return manager
@staticmethod
@@ -82,7 +73,6 @@ class BTNap:
iface = obj.dbus_interface
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
-
@staticmethod
def find_adapter(pattern=None):
"""
@@ -98,14 +88,14 @@ class BTNap:
"""
bus, obj = BTNap.get_bus(), None
for path, ifaces in objects.items():
- adapter = ifaces.get(BTNap.IFACE_ADAPTER)
- if adapter is None:
- continue
- if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
- obj = bus.get_object(BTNap.IFACE_BASE, path)
- yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
+ adapter = ifaces.get(BTNap.IFACE_ADAPTER)
+ if adapter is None:
+ continue
+ if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
+ obj = bus.get_object(BTNap.IFACE_BASE, path)
+ yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
if obj is None:
- raise BTError('Bluetooth adapter not found')
+ raise BTError('Bluetooth adapter not found')
@staticmethod
def find_device(device_address, adapter_pattern=None):
@@ -178,7 +168,6 @@ class BTNap:
logging.debug("BT-TETHER: Device is not connected.")
return None, False
-
def is_paired(self):
"""
Check if already connected
@@ -198,7 +187,6 @@ class BTNap:
logging.debug("BT-TETHER: Device is not paired.")
return False
-
def wait_for_device(self, timeout=15):
"""
Wait for device
@@ -227,7 +215,7 @@ class BTNap:
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
- BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
+ BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path)
break
except BTError:
logging.debug("BT-TETHER: Not found yet ...")
@@ -259,7 +247,6 @@ class BTNap:
pass
return False
-
@staticmethod
def nap(device):
logging.debug('BT-TETHER: Trying to nap ...')
@@ -267,7 +254,7 @@ class BTNap:
try:
logging.debug('BT-TETHER: Connecting to profile ...')
device.ConnectProfile('nap')
- except Exception: # raises exception, but still works
+ except Exception: # raises exception, but still works
pass
net = dbus.Interface(device, 'org.bluez.Network1')
@@ -297,7 +284,7 @@ class SystemdUnitWrapper:
@staticmethod
def _action_on_unit(action, unit):
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
- stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+ stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
@@ -309,7 +296,7 @@ class SystemdUnitWrapper:
Calls systemctl daemon-reload
"""
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
- stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+ stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
@@ -387,16 +374,15 @@ class IfaceWrapper:
"""
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
-
def set_addr(self, addr):
"""
Set the netmask
"""
process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
- stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+ stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
- if process.returncode == 2 or process.returncode == 0: # 2 = already set
+ if process.returncode == 2 or process.returncode == 0: # 2 = already set
return True
return False
@@ -404,7 +390,7 @@ class IfaceWrapper:
@staticmethod
def set_route(addr):
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
- stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
+ stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
@@ -413,44 +399,47 @@ class IfaceWrapper:
return True
+class BTTether(plugins.Plugin):
+ __author__ = '33197631+dadav@users.noreply.github.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'This makes the display reachable over bluetooth'
-def on_loaded():
- """
- Gets called when the plugin gets loaded
- """
- global READY
- global INTERVAL
+ def __init__(self):
+ self.ready = False
+ self.interval = StatusFile('/root/.bt-tether')
+ self.network = None
- for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
- if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
- logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
+ def on_loaded(self):
+ for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
+ if opt not in self.options or (opt in self.options and self.options[opt] is None):
+ logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
+ return
+
+ # ensure bluetooth is running
+ bt_unit = SystemdUnitWrapper('bluetooth.service')
+ if not bt_unit.is_active():
+ if not bt_unit.start():
+ logging.error("BT-TET: Can't start bluetooth.service")
+ return
+
+ self.interval.update()
+ self.ready = True
+
+ def on_ui_setup(self, 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))
+
+ def on_ui_update(self, ui):
+ if not self.ready:
return
- # ensure bluetooth is running
- bt_unit = SystemdUnitWrapper('bluetooth.service')
- if not bt_unit.is_active():
- if not bt_unit.start():
- logging.error("BT-TET: Can't start bluetooth.service")
+ if self.interval.newer_then_minutes(self.options['interval']):
return
- INTERVAL.update()
- READY = True
+ self.interval.update()
-
-def on_ui_update(ui):
- """
- Try to connect to device
- """
-
- if READY:
- global INTERVAL
- global NETWORK
- if INTERVAL.newer_then_minutes(OPTIONS['interval']):
- return
-
- INTERVAL.update()
-
- bt = BTNap(OPTIONS['mac'])
+ bt = BTNap(self.options['mac'])
logging.debug('BT-TETHER: Check if already connected and paired')
dev_remote, connected = bt.is_connected()
@@ -483,14 +472,13 @@ def on_ui_update(ui):
else:
logging.debug('BT-TETHER: Already paired.')
-
btnap_iface = IfaceWrapper('bnep0')
logging.debug('BT-TETHER: Check interface')
if not btnap_iface.exists():
# connected and paired but not napping
logging.debug('BT-TETHER: Try to connect to nap ...')
network, status = BTNap.nap(dev_remote)
- NETWORK = network
+ self.network = network
if status:
logging.info('BT-TETHER: Napping!')
ui.set('bluetooth', 'C')
@@ -504,7 +492,7 @@ def on_ui_update(ui):
logging.debug('BT-TETHER: Interface found')
# check ip
- addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
+ addr = f"{self.options['ip']}/{self.options['netmask']}"
logging.debug('BT-TETHER: Try to set ADDR to interface')
if not btnap_iface.set_addr(addr):
@@ -515,9 +503,10 @@ def on_ui_update(ui):
logging.debug('BT-TETHER: Set ADDR to interface')
# change route if sharking
- if OPTIONS['share_internet']:
+ if self.options['share_internet']:
logging.debug('BT-TETHER: Set routing and change resolv.conf')
- IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that
+ IfaceWrapper.set_route(
+ ".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that
# fix resolv.conf; dns over https ftw!
with open('/etc/resolv.conf', 'r+') as resolv:
nameserver = resolv.read()
@@ -530,8 +519,3 @@ def on_ui_update(ui):
else:
logging.error('BT-TETHER: bnep0 not found')
ui.set('bluetooth', 'BE')
-
-
-def on_ui_setup(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))
diff --git a/pwnagotchi/plugins/default/example.py b/pwnagotchi/plugins/default/example.py
index 72087cfb..3aa5c7d8 100644
--- a/pwnagotchi/plugins/default/example.py
+++ b/pwnagotchi/plugins/default/example.py
@@ -1,182 +1,154 @@
-__author__ = 'evilsocket@gmail.com'
-__version__ = '1.0.0'
-__name__ = 'hello_world'
-__license__ = 'GPL3'
-__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
-
import logging
+import pwnagotchi.plugins as plugins
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
-# Will be set with the options in config.yml config['main']['plugins'][__name__]
-OPTIONS = dict()
+class Example(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
-# called when :/plugins/ is opened
-def on_webhook(response, path):
- res = "Hook triggered"
- response.send_response(200)
- response.send_header('Content-type', 'text/html')
- response.end_headers()
+ def __init__(self):
+ logging.debug("example plugin created")
- try:
- response.wfile.write(bytes(res, "utf-8"))
- except Exception as ex:
- logging.error(ex)
+ # called when the plugin is loaded
+ def on_loaded(self):
+ logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
-# called when the plugin is loaded
-def on_loaded():
- logging.warning("WARNING: plugin %s should be disabled!" % __name__)
+ # called when :/plugins/ is opened
+ def on_webhook(self, response, path):
+ res = "Hook triggered"
+ response.send_response(200)
+ response.send_header('Content-type', 'text/html')
+ response.end_headers()
+ try:
+ response.wfile.write(bytes(res, "utf-8"))
+ except Exception as ex:
+ logging.error(ex)
-# called in manual mode when there's internet connectivity
-def on_internet_available(agent):
- pass
+ # called in manual mode when there's internet connectivity
+ def on_internet_available(self, agent):
+ pass
+ # called to setup the ui elements
+ def on_ui_setup(self, ui):
+ # add custom UI elements
+ ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
+ label_font=fonts.Bold, text_font=fonts.Medium))
-# called to setup the ui elements
-def on_ui_setup(ui):
- # add custom UI elements
- ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
- label_font=fonts.Bold, text_font=fonts.Medium))
+ # called when the ui is updated
+ def on_ui_update(self, ui):
+ # update those elements
+ some_voltage = 0.1
+ some_capacity = 100.0
+ ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
+ # called when the hardware display setup is done, display is an hardware specific object
+ def on_display_setup(self, display):
+ pass
-# called when the ui is updated
-def on_ui_update(ui):
- # update those elements
- some_voltage = 0.1
- some_capacity = 100.0
+ # 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()
- ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
+ # 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 hardware display setup is done, display is an hardware specific object
-def on_display_setup(display):
- 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 everything is ready and the main loop is about to start
-def on_ready(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 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 finished loading
-def on_ai_ready(agent):
- 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 AI finds a new set of parameters
-def on_ai_policy(agent, policy):
- 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 AI starts training for a given number of epochs
-def on_ai_training_start(agent, epochs):
- pass
+ # called when the status is set to excited
+ def on_excited(aself, gent):
+ pass
+ # called when the status is set to lonely
+ def on_lonely(self, agent):
+ pass
-# called after the AI completed a training epoch
-def on_ai_training_step(agent, _locals, _globals):
- 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 AI has done training
-def on_ai_training_end(agent):
- 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 AI got the best reward so far
-def on_ai_best_reward(agent, reward):
- 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
-# called when the AI got the worst reward so far
-def on_ai_worst_reward(agent, reward):
- 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 a non overlapping wifi channel is found to be free
-def on_free_channel(agent, channel):
- 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 the status is set to bored
-def on_bored(agent):
- pass
-
-
-# called when the status is set to sad
-def on_sad(agent):
- pass
-
-
-# called when the status is set to excited
-def on_excited(agent):
- pass
-
-
-# called when the status is set to lonely
-def on_lonely(agent):
- pass
-
-
-# called when the agent is rebooting the board
-def on_rebooting(agent):
- pass
-
-
-# called when the agent is waiting for t seconds
-def on_wait(agent, t):
- pass
-
-
-# called when the agent is sleeping for t seconds
-def on_sleep(agent, t):
- pass
-
-
-# called when the agent refreshed its access points list
-def on_wifi_update(agent, access_points):
- pass
-
-
-# called when the agent is sending an association frame
-def on_association(agent, access_point):
- pass
-
-
-# callend when the agent is deauthenticating a client station from an AP
-def on_deauthentication(agent, access_point, client_station):
- pass
-
-
-# callend when the agent is tuning on a specific channel
-def on_channel_hop(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(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(agent, epoch, epoch_data):
- pass
-
-
-# called when a new peer is detected
-def on_peer_detected(agent, peer):
- pass
-
-
-# called when a known peer is lost
-def on_peer_lost(agent, peer):
- pass
+ # called when a known peer is lost
+ def on_peer_lost(self, agent, peer):
+ pass
diff --git a/pwnagotchi/plugins/default/gpio_buttons.py b/pwnagotchi/plugins/default/gpio_buttons.py
index c0f6ceb1..7d9adb87 100644
--- a/pwnagotchi/plugins/default/gpio_buttons.py
+++ b/pwnagotchi/plugins/default/gpio_buttons.py
@@ -1,38 +1,40 @@
-__author__ = 'ratmandu@gmail.com'
-__version__ = '1.0.0'
-__name__ = 'gpio_buttons'
-__license__ = 'GPL3'
-__description__ = 'GPIO Button support plugin'
-
import logging
import RPi.GPIO as GPIO
import subprocess
-
-running = False
-OPTIONS = dict()
-GPIOs = {}
-COMMANDs = None
-
-def runCommand(channel):
- command = GPIOs[channel]
- logging.info(f"Button Pressed! Running command: {command}")
- process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
- process.wait()
+import pwnagotchi.plugins as plugins
-def on_loaded():
- logging.info("GPIO Button plugin loaded.")
-
- #get list of GPIOs
- gpios = OPTIONS['gpios']
+class GPIOButtons(plugins.Plugin):
+ __author__ = 'ratmandu@gmail.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'GPIO Button support plugin'
- #set gpio numbering
- GPIO.setmode(GPIO.BCM)
+ def __init__(self):
+ self.running = False
+ self.ports = {}
+ self.commands = None
- for i in gpios:
- gpio = list(i)[0]
- command = i[gpio]
- GPIOs[gpio] = command
- GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
- GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250)
- logging.info("Added command: %s to GPIO #%d", command, gpio)
+ def runCommand(self, channel):
+ command = self.ports[channel]
+ logging.info(f"Button Pressed! Running command: {command}")
+ process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
+ executable="/bin/bash")
+ process.wait()
+
+ def on_loaded(self):
+ logging.info("GPIO Button plugin loaded.")
+
+ # get list of GPIOs
+ gpios = self.options['gpios']
+
+ # set gpio numbering
+ GPIO.setmode(GPIO.BCM)
+
+ for i in gpios:
+ gpio = list(i)[0]
+ command = i[gpio]
+ self.ports[gpio] = command
+ GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
+ GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
+ logging.info("Added command: %s to GPIO #%d", command, gpio)
diff --git a/pwnagotchi/plugins/default/gps.py b/pwnagotchi/plugins/default/gps.py
index 234df3a9..01945597 100644
--- a/pwnagotchi/plugins/default/gps.py
+++ b/pwnagotchi/plugins/default/gps.py
@@ -1,45 +1,42 @@
-__author__ = 'evilsocket@gmail.com'
-__version__ = '1.0.0'
-__name__ = 'gps'
-__license__ = 'GPL3'
-__description__ = 'Save GPS coordinates whenever an handshake is captured.'
-
import logging
import json
import os
-
-running = False
-OPTIONS = dict()
+import pwnagotchi.plugins as plugins
-def on_loaded():
- logging.info("gps plugin loaded for %s" % OPTIONS['device'])
+class GPS(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'Save GPS coordinates whenever an handshake is captured.'
+ def __init__(self):
+ self.running = False
-def on_ready(agent):
- global running
+ def on_loaded(self):
+ logging.info("gps plugin loaded for %s" % self.options['device'])
- if os.path.exists(OPTIONS['device']):
- logging.info("enabling gps bettercap's module for %s" % OPTIONS['device'])
- try:
- agent.run('gps off')
- except:
- pass
+ def on_ready(self, agent):
+ if os.path.exists(self.options['device']):
+ logging.info("enabling gps bettercap's module for %s" % self.options['device'])
+ try:
+ agent.run('gps off')
+ except:
+ pass
- agent.run('set gps.device %s' % OPTIONS['device'])
- agent.run('set gps.speed %d' % OPTIONS['speed'])
- agent.run('gps on')
- running = True
- else:
- logging.warning("no GPS detected")
+ agent.run('set gps.device %s' % self.options['device'])
+ agent.run('set gps.speed %d' % self.options['speed'])
+ agent.run('gps on')
+ running = True
+ else:
+ logging.warning("no GPS detected")
+ def on_handshake(self, agent, filename, access_point, client_station):
+ if self.running:
+ info = agent.session()
+ gps = info['gps']
+ gps_filename = filename.replace('.pcap', '.gps.json')
-def on_handshake(agent, filename, access_point, client_station):
- if running:
- info = agent.session()
- gps = info['gps']
- gps_filename = filename.replace('.pcap', '.gps.json')
-
- logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
- with open(gps_filename, 'w+t') as fp:
- json.dump(gps, fp)
+ logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
+ with open(gps_filename, 'w+t') as fp:
+ json.dump(gps, fp)
diff --git a/pwnagotchi/plugins/default/grid.py b/pwnagotchi/plugins/default/grid.py
index 9ffd61f7..58609394 100644
--- a/pwnagotchi/plugins/default/grid.py
+++ b/pwnagotchi/plugins/default/grid.py
@@ -1,10 +1,3 @@
-__author__ = 'evilsocket@gmail.com'
-__version__ = '1.0.1'
-__name__ = 'grid'
-__license__ = 'GPL3'
-__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
- 'networks to api.pwnagotchi.ai '
-
import os
import logging
import time
@@ -12,18 +5,9 @@ import glob
import re
import pwnagotchi.grid as grid
+import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
-OPTIONS = dict()
-REPORT = StatusFile('/root/.api-report.json', data_format='json')
-
-UNREAD_MESSAGES = 0
-TOTAL_MESSAGES = 0
-
-
-def on_loaded():
- logging.info("grid plugin loaded.")
-
def parse_pcap(filename):
logging.info("grid: parsing %s ..." % filename)
@@ -57,93 +41,100 @@ def parse_pcap(filename):
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
-def is_excluded(what):
- for skip in OPTIONS['exclude']:
- skip = skip.lower()
- what = what.lower()
- if skip in what or skip.replace(':', '') in what:
- return True
- return False
+class Grid(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __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')
-def set_reported(reported, net_id):
- global REPORT
- reported.append(net_id)
- REPORT.update(data={'reported': reported})
+ 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 check_inbox(agent):
- global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
+ def on_loaded(self):
+ logging.info("grid plugin loaded.")
- logging.debug("checking mailbox ...")
+ def set_reported(self, reported, net_id):
+ reported.append(net_id)
+ self.report.update(data={'reported': reported})
- messages = grid.inbox()
- TOTAL_MESSAGES = len(messages)
- UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
+ 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 UNREAD_MESSAGES:
- logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
- agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
+ if 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")
-def check_handshakes(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
- pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
- num_networks = len(pcap_files)
- reported = 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'])
- if num_new > 0:
- if OPTIONS['report']:
- logging.info("grid: %d new networks to report" % num_new)
- logging.debug("OPTIONS: %s" % OPTIONS)
- logging.debug(" exclude: %s" % 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
- for pcap_file in pcap_files:
- net_id = os.path.basename(pcap_file).replace('.pcap', '')
- if net_id not in reported:
- if is_excluded(net_id):
- logging.debug("skipping %s due to exclusion filter" % pcap_file)
- set_reported(reported, net_id)
- continue
-
- essid, bssid = parse_pcap(pcap_file)
- if bssid:
- if is_excluded(essid) or is_excluded(bssid):
- logging.debug("not reporting %s due to exclusion filter" % pcap_file)
- set_reported(reported, net_id)
+ 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:
- if grid.report_ap(essid, bssid):
- set_reported(reported, net_id)
- time.sleep(1.5)
- else:
- logging.warning("no bssid found?!")
- else:
- logging.debug("grid: reporting disabled")
+ logging.warning("no bssid found?!")
+ else:
+ logging.debug("grid: reporting disabled")
+ def on_internet_available(self, agent):
+ logging.debug("internet available")
-def on_internet_available(agent):
- global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
+ 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
- logging.debug("internet available")
+ 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:
- 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:
- check_inbox(agent)
- except Exception as e:
- logging.error("[grid] error while checking inbox: %s" % e)
- logging.debug(e, exc_info=True)
-
- try:
- check_handshakes(agent)
- except Exception as e:
- logging.error("[grid] error while checking pcaps: %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)
diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py
index 3d6682b6..521aa655 100644
--- a/pwnagotchi/plugins/default/memtemp.py
+++ b/pwnagotchi/plugins/default/memtemp.py
@@ -17,48 +17,44 @@
# - Added horizontal and vertical orientation
#
###############################################################
-
-__author__ = 'https://github.com/xenDE'
-__version__ = '1.0.1'
-__name__ = 'memtemp'
-__license__ = 'GPL3'
-__description__ = 'A plugin that will display memory/cpu usage and temperature'
-
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
+import pwnagotchi.plugins as plugins
import pwnagotchi
import logging
-OPTIONS = dict()
+class MemTemp(plugins.Plugin):
+ __author__ = 'https://github.com/xenDE'
+ __version__ = '1.0.1'
+ __license__ = 'GPL3'
+ __description__ = 'A plugin that will display memory/cpu usage and temperature'
-def on_loaded():
- logging.info("memtemp plugin loaded.")
+ def on_loaded(self):
+ logging.info("memtemp plugin loaded.")
+ def mem_usage(self):
+ return int(pwnagotchi.mem_usage() * 100)
-def mem_usage():
- return int(pwnagotchi.mem_usage() * 100)
+ def cpu_load(self):
+ return int(pwnagotchi.cpu_load() * 100)
+ def on_ui_setup(self, ui):
+ if self.options['orientation'] == "horizontal":
+ ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
+ position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
+ label_font=fonts.Small, text_font=fonts.Small))
+ elif self.options['orientation'] == "vertical":
+ ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
+ position=(ui.width() / 2 + 55, ui.height() / 2),
+ label_font=fonts.Small, text_font=fonts.Small))
-def cpu_load():
- return int(pwnagotchi.cpu_load() * 100)
+ def on_ui_update(self, ui):
+ if self.options['orientation'] == "horizontal":
+ ui.set('memtemp',
+ " mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
-
-def on_ui_setup(ui):
- if OPTIONS['orientation'] == "horizontal":
- ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
- position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
- label_font=fonts.Small, text_font=fonts.Small))
- elif OPTIONS['orientation'] == "vertical":
- ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
- position=(ui.width() / 2 + 55, ui.height() / 2),
- label_font=fonts.Small, text_font=fonts.Small))
-
-
-def on_ui_update(ui):
- if OPTIONS['orientation'] == "horizontal":
- ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
-
- elif OPTIONS['orientation'] == "vertical":
- ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
+ elif self.options['orientation'] == "vertical":
+ ui.set('memtemp',
+ " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
diff --git a/pwnagotchi/plugins/default/net-pos.py b/pwnagotchi/plugins/default/net-pos.py
index d0c54ae0..3f1f4e5a 100644
--- a/pwnagotchi/plugins/default/net-pos.py
+++ b/pwnagotchi/plugins/default/net-pos.py
@@ -1,140 +1,134 @@
-__author__ = 'zenzen san'
-__version__ = '2.0.0'
-__name__ = 'net-pos'
-__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 """
-
import logging
import json
import os
import requests
+import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
-REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
-SKIP = list()
-READY = False
-OPTIONS = dict()
-def on_loaded():
- global READY
+class NetPos(plugins.Plugin):
+ __author__ = 'zenzen san'
+ __version__ = '2.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 """
- if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
- logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
- return
+ def __init__(self):
+ self.report = StatusFile('/root/.net_pos_saved', data_format='json')
+ self.skip = list()
+ self.ready = False
- READY = True
+ 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.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
+ return
- logging.info("net-pos plugin loaded.")
+ self.ready = True
+ logging.info("net-pos plugin loaded.")
-def _append_saved(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))
+ 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")
+ with open('/root/.net_pos_saved', 'a') as saved_file:
+ for x in to_save:
+ saved_file.write(x + "\n")
-def on_internet_available(agent):
- global SKIP
- global REPORT
+ def on_internet_available(self, agent):
+ if self.ready:
+ config = agent.config()
+ display = agent.view()
+ reported = self.report.data_field_or('reported', default=list())
+ handshake_dir = config['bettercap']['handshakes']
- if READY:
- config = agent.config()
- display = agent.view()
- reported = 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)
- 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(SKIP)
-
- if new_np_files:
- logging.info("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):
-
- geo_file = np_file.replace('.net-pos.json', '.geo.json')
- if os.path.exists(geo_file):
- # got already the position
- reported.append(np_file)
- REPORT.update(data={'reported': reported})
- continue
-
- try:
- geo_data = _get_geo_data(np_file) # returns json obj
- except requests.exceptions.RequestException as req_e:
- logging.error("NET-POS: %s", req_e)
- SKIP += np_file
- continue
- except json.JSONDecodeError as js_e:
- logging.error("NET-POS: %s", js_e)
- SKIP += np_file
- continue
- except OSError as os_e:
- logging.error("NET-POS: %s", os_e)
- SKIP += np_file
- continue
-
- with open(geo_file, 'w+t') as sf:
- json.dump(geo_data, sf)
-
- reported.append(np_file)
- REPORT.update(data={'reported': reported})
-
- display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
+ if new_np_files:
+ logging.info("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):
+ 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
-def on_handshake(agent, filename, access_point, client_station):
- netpos = _get_netpos(agent)
- netpos_filename = filename.replace('.pcap', '.net-pos.json')
- logging.info("NET-POS: Saving net-location to %s", netpos_filename)
+ try:
+ geo_data = self._get_geo_data(np_file) # returns json obj
+ except requests.exceptions.RequestException as req_e:
+ logging.error("NET-POS: %s", req_e)
+ self.skip += np_file
+ continue
+ except json.JSONDecodeError as js_e:
+ logging.error("NET-POS: %s", js_e)
+ self.skip += np_file
+ continue
+ except OSError as os_e:
+ logging.error("NET-POS: %s", os_e)
+ self.skip += np_file
+ continue
- 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)
+ with open(geo_file, 'w+t') as sf:
+ json.dump(geo_data, sf)
+ reported.append(np_file)
+ self.report.update(data={'reported': reported})
-def _get_netpos(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
+ display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
+ display.update(force=True)
-def _get_geo_data(path, timeout=30):
- geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key'])
+ def on_handshake(self, agent, filename, access_point, client_station):
+ netpos = self._get_netpos(agent)
+ netpos_filename = filename.replace('.pcap', '.net-pos.json')
+ logging.info("NET-POS: Saving net-location to %s", netpos_filename)
- 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:
+ 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)
- try:
- result = requests.post(geourl,
- json=data,
- timeout=timeout)
- return result.json()
- except requests.exceptions.RequestException as req_e:
- raise req_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 = MOZILLA_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 result.json()
+ except requests.exceptions.RequestException as req_e:
+ raise req_e
diff --git a/pwnagotchi/plugins/default/onlinehashcrack.py b/pwnagotchi/plugins/default/onlinehashcrack.py
index 47f881a0..7a02a300 100644
--- a/pwnagotchi/plugins/default/onlinehashcrack.py
+++ b/pwnagotchi/plugins/default/onlinehashcrack.py
@@ -1,86 +1,82 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '2.0.0'
-__name__ = 'onlinehashcrack'
-__license__ = 'GPL3'
-__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
-
import os
import logging
import requests
from pwnagotchi.utils import StatusFile
-
-READY = False
-REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
-SKIP = list()
-OPTIONS = dict()
+import pwnagotchi.plugins as plugins
-def on_loaded():
- """
- Gets called when the plugin gets loaded
- """
- global READY
+class OnlineHashCrack(plugins.Plugin):
+ __author__ = '33197631+dadav@users.noreply.github.com'
+ __version__ = '2.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
- if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
- logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
- return
+ def __init__(self):
+ self.ready = False
+ self.report = StatusFile('/root/.ohc_uploads', data_format='json')
+ self.skip = list()
- READY = True
+ 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(path, timeout=30):
- """
- Uploads the file to onlinehashcrack.com
- """
- with open(path, 'rb') as file_to_upload:
- data = {'email': OPTIONS['email']}
- payload = {'file': file_to_upload}
+ 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
+ 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())
-def on_internet_available(agent):
- """
- Called in manual mode when there's internet connectivity
- """
- global REPORT
- global SKIP
- if READY:
- display = agent.view()
- config = agent.config()
- reported = 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(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:
- _upload_to_ohc(handshake)
- reported.append(handshake)
- REPORT.update(data={'reported': reported})
- logging.info(f"OHC: Successfully uploaded {handshake}")
- except requests.exceptions.RequestException as req_e:
- SKIP.append(handshake)
- logging.error("OHC: %s", req_e)
- continue
- except OSError as os_e:
- SKIP.append(handshake)
- logging.error("OHC: %s", os_e)
- continue
+ 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
diff --git a/pwnagotchi/plugins/default/paw-gps.py b/pwnagotchi/plugins/default/paw-gps.py
index a5268c42..a6810334 100644
--- a/pwnagotchi/plugins/default/paw-gps.py
+++ b/pwnagotchi/plugins/default/paw-gps.py
@@ -1,27 +1,27 @@
-__author__ = 'leont'
-__version__ = '1.0.0'
-__name__ = 'pawgps'
-__license__ = 'GPL3'
-__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
+import logging
+import requests
+import pwnagotchi.plugins as plugins
'''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic:
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
'''
-import logging
-import requests
-OPTIONS = dict()
+class PawGPS(plugins.Plugin):
+ __author__ = 'leont'
+ __version__ = '1.0.0'
+ __name__ = 'pawgps'
+ __license__ = 'GPL3'
+ __description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
+ def on_loaded(self):
+ logging.info("PAW-GPS loaded")
+ if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
+ logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
-def on_loaded():
- logging.info("PAW-GPS loaded")
- if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
- logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
-
-def on_handshake(agent, filename, access_point, client_station):
- if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
+ def on_handshake(self, agent, filename, access_point, client_station):
+ if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
ip = "192.168.44.1"
gps = requests.get('http://' + ip + '/gps.xhtml')
diff --git a/pwnagotchi/plugins/default/quickdic.py b/pwnagotchi/plugins/default/quickdic.py
index b898b21a..7bdd6470 100644
--- a/pwnagotchi/plugins/default/quickdic.py
+++ b/pwnagotchi/plugins/default/quickdic.py
@@ -1,8 +1,8 @@
-__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
-__version__ = '1.0.0'
-__name__ = 'quickdic'
-__license__ = 'GPL3'
-__description__ = 'Run a quick dictionary scan against captured handshakes'
+import logging
+import subprocess
+import string
+import re
+import pwnagotchi.plugins as plugins
'''
Aircrack-ng needed, to install:
@@ -11,42 +11,41 @@ Upload wordlist files in .txt format to folder in config file (Default: /opt/wor
Cracked handshakes stored in handshake folder as [essid].pcap.cracked
'''
-import logging
-import subprocess
-import string
-import re
-OPTIONS = dict()
+class QuickDic(plugins.Plugin):
+ __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'Run a quick dictionary scan against captured handshakes'
-def on_loaded():
- logging.info("Quick dictionary check plugin loaded")
+ def __init__(self):
+ self.text_to_set = ""
-def on_handshake(agent, filename, access_point, client_station):
- display = agent._view
+ def on_loaded(self):
+ logging.info("Quick dictionary check plugin loaded")
- result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
- result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
- if not result:
- logging.info("[quickdic] No handshake")
- else:
- logging.info("[quickdic] Handshake confirmed")
- result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE)
- result2 = result2.stdout.decode('utf-8').strip()
- logging.info("[quickdic] "+result2)
- if result2 != "KEY NOT FOUND":
- key = re.search('\[(.*)\]', result2)
- pwd = str(key.group(1))
- set_text("Cracked password: "+pwd)
- display.update(force=True)
+ def on_handshake(self, agent, filename, access_point, client_station):
+ display = agent.view()
+ result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
+ shell=True, stdout=subprocess.PIPE)
+ result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
+ if not result:
+ logging.info("[quickdic] No handshake")
+ else:
+ logging.info("[quickdic] Handshake confirmed")
+ result2 = subprocess.run(('aircrack-ng -w `echo ' + self.options[
+ 'wordlist_folder'] + '*.txt | sed \'s/\ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'),
+ shell=True, stdout=subprocess.PIPE)
+ result2 = result2.stdout.decode('utf-8').strip()
+ logging.info("[quickdic] " + result2)
+ if result2 != "KEY NOT FOUND":
+ key = re.search('\[(.*)\]', result2)
+ pwd = str(key.group(1))
+ self.text_to_set = "Cracked password: " + pwd
+ display.update(force=True)
-text_to_set = "";
-def set_text(text):
- global text_to_set
- text_to_set = text
-
-def on_ui_update(ui):
- global text_to_set
- if text_to_set:
- ui.set('face', "(·ω·)")
- ui.set('status', text_to_set)
- text_to_set = ""
+ def on_ui_update(self, ui):
+ if self.text_to_set:
+ ui.set('face', "(·ω·)")
+ ui.set('status', self.text_to_set)
+ self.text_to_set = ""
diff --git a/pwnagotchi/plugins/default/screen_refresh.py b/pwnagotchi/plugins/default/screen_refresh.py
index 3c7047fa..25ea3252 100644
--- a/pwnagotchi/plugins/default/screen_refresh.py
+++ b/pwnagotchi/plugins/default/screen_refresh.py
@@ -1,24 +1,23 @@
-__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
-__version__ = '1.0.0'
-__name__ = 'screen_refresh'
-__license__ = 'GPL3'
-__description__ = 'Refresh he e-ink display after X amount of updates'
-
import logging
-
-OPTIONS = dict()
-update_count = 0;
+import pwnagotchi.plugins as plugins
-def on_loaded():
- logging.info("Screen refresh plugin loaded")
+class ScreenRefresh(plugins.Plugin):
+ __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'Refresh he e-ink display after X amount of updates'
+ def __init__(self):
+ self.update_count = 0;
-def on_ui_update(ui):
- global update_count
- update_count += 1
- if update_count == OPTIONS['refresh_interval']:
- ui.init_display()
- ui.set('status', "Screen cleaned")
- logging.info("Screen refreshing")
- update_count = 0
+ def on_loaded(self):
+ logging.info("Screen refresh plugin loaded")
+
+ def on_ui_update(self, ui):
+ self.update_count += 1
+ if self.update_count == self.options['refresh_interval']:
+ ui.init_display()
+ ui.set('status', "Screen cleaned")
+ logging.info("Screen refreshing")
+ self.update_count = 0
diff --git a/pwnagotchi/plugins/default/twitter.py b/pwnagotchi/plugins/default/twitter.py
index fd247ca4..80bb2bac 100644
--- a/pwnagotchi/plugins/default/twitter.py
+++ b/pwnagotchi/plugins/default/twitter.py
@@ -1,50 +1,49 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '1.0.0'
-__name__ = 'twitter'
-__license__ = 'GPL3'
-__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
-
import logging
from pwnagotchi.voice import Voice
-
-OPTIONS = dict()
-
-def on_loaded():
- logging.info("twitter plugin loaded.")
+import pwnagotchi.plugins as plugins
-# called in manual mode when there's internet connectivity
-def on_internet_available(agent):
- config = agent.config()
- display = agent.view()
- last_session = agent.last_session
+class Twitter(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
- if last_session.is_new() and last_session.handshakes > 0:
- try:
- import tweepy
- except ImportError:
- logging.error("Couldn't import tweepy")
- return
+ def on_loaded(self):
+ logging.info("twitter plugin loaded.")
- logging.info("detected a new session and internet connectivity!")
+ # called in manual mode when there's internet connectivity
+ def on_internet_available(self, agent):
+ config = agent.config()
+ display = agent.view()
+ last_session = agent.last_session
- picture = '/dev/shm/pwnagotchi.png'
+ if last_session.is_new() and last_session.handshakes > 0:
+ try:
+ import tweepy
+ except ImportError:
+ logging.error("Couldn't import tweepy")
+ return
- display.on_manual_mode(last_session)
- display.update(force=True)
- display.image().save(picture, 'png')
- display.set('status', 'Tweeting...')
- display.update(force=True)
+ logging.info("detected a new session and internet connectivity!")
- try:
- auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
- auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
- api = tweepy.API(auth)
+ picture = '/root/pwnagotchi.png'
- tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
- api.update_with_media(filename=picture, status=tweet)
- last_session.save_session_id()
+ display.on_manual_mode(last_session)
+ display.update(force=True)
+ display.image().save(picture, 'png')
+ display.set('status', 'Tweeting...')
+ display.update(force=True)
- logging.info("tweeted: %s" % tweet)
- except Exception as e:
- logging.exception("error while tweeting")
+ try:
+ auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret'])
+ auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret'])
+ api = tweepy.API(auth)
+
+ tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
+ api.update_with_media(filename=picture, status=tweet)
+ last_session.save_session_id()
+
+ logging.info("tweeted: %s" % tweet)
+ except Exception as e:
+ logging.exception("error while tweeting")
diff --git a/pwnagotchi/plugins/default/unfiltered_example.py b/pwnagotchi/plugins/default/unfiltered_example.py
deleted file mode 100644
index 9fe1e496..00000000
--- a/pwnagotchi/plugins/default/unfiltered_example.py
+++ /dev/null
@@ -1,22 +0,0 @@
-__author__ = 'diemelcw@gmail.com'
-__version__ = '1.0.0'
-__name__ = 'unfiltered_example'
-__license__ = 'GPL3'
-__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
-
-import logging
-
-# Will be set with the options in config.yml config['main']['plugins'][__name__]
-OPTIONS = dict()
-
-# called when the plugin is loaded
-def on_loaded():
- logging.warning("%s plugin loaded" % __name__)
-
-# called when AP list is ready, before whitelist filtering has occurred
-def on_unfiltered_ap_list(agent,aps):
- logging.info("Unfiltered AP list to follow")
- for ap in aps:
- logging.info(ap['hostname'])
-
- ## Additional logic here ##
diff --git a/pwnagotchi/plugins/default/ups_lite.py b/pwnagotchi/plugins/default/ups_lite.py
index 5c0d9d7b..74e0b627 100644
--- a/pwnagotchi/plugins/default/ups_lite.py
+++ b/pwnagotchi/plugins/default/ups_lite.py
@@ -7,17 +7,12 @@
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
# https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310
# https://www.aliexpress.com/item/32888533624.html
-__author__ = 'evilsocket@gmail.com'
-__version__ = '1.0.0'
-__name__ = 'ups_lite'
-__license__ = 'GPL3'
-__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
-
import struct
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
+import pwnagotchi.plugins as plugins
# TODO: add enable switch in config.yml an cleanup all to the best place
@@ -47,18 +42,21 @@ class UPS:
return 0.0
-ups = None
+class UPSLite(plugins.Plugin):
+ __author__ = 'evilsocket@gmail.com'
+ __version__ = '1.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
+ def __init__(self):
+ self.ups = None
-def on_loaded():
- global ups
- ups = UPS()
+ def on_loaded(self):
+ self.ups = UPS()
+ def on_ui_setup(self, ui):
+ ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
+ label_font=fonts.Bold, text_font=fonts.Medium))
-def on_ui_setup(ui):
- ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
- label_font=fonts.Bold, text_font=fonts.Medium))
-
-
-def on_ui_update(ui):
- ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity()))
+ def on_ui_update(self, ui):
+ ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity()))
diff --git a/pwnagotchi/plugins/default/wigle.py b/pwnagotchi/plugins/default/wigle.py
index 5437132b..d0d748f5 100644
--- a/pwnagotchi/plugins/default/wigle.py
+++ b/pwnagotchi/plugins/default/wigle.py
@@ -1,9 +1,3 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '2.0.0'
-__name__ = 'wigle'
-__license__ = 'GPL3'
-__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
-
import os
import logging
import json
@@ -12,24 +6,7 @@ import csv
from datetime import datetime
import requests
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
-
-READY = False
-REPORT = StatusFile('/root/.wigle_uploads', data_format='json')
-SKIP = list()
-OPTIONS = dict()
-
-
-def on_loaded():
- """
- Gets called when the plugin gets loaded
- """
- global READY
-
- if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
- logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
- return
-
- READY = True
+import pwnagotchi.plugins as plugins
def _extract_gps_data(path):
@@ -54,14 +31,17 @@ def _format_auth(data):
out = f"{out}[{auth}]"
return out
+
def _transform_wigle_entry(gps_data, pcap_data):
"""
Transform to wigle entry in file
"""
dummy = StringIO()
# write kismet header
- dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
- dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
+ dummy.write(
+ "WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
+ dummy.write(
+ "MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
writer.writerow([
@@ -75,10 +55,11 @@ def _transform_wigle_entry(gps_data, pcap_data):
gps_data['Latitude'],
gps_data['Longitude'],
gps_data['Altitude'],
- 0, # accuracy?
+ 0, # accuracy?
'WIFI'])
return dummy.getvalue()
+
def _send_to_wigle(lines, api_key, timeout=30):
"""
Uploads the file to wigle-net
@@ -109,87 +90,100 @@ def _send_to_wigle(lines, api_key, timeout=30):
raise re_e
-def on_internet_available(agent):
- from scapy.all import Scapy_Exception
- """
- Called in manual mode when there's internet connectivity
- """
- global REPORT
- global SKIP
+class Wigle(plugins.Plugin):
+ __author__ = '33197631+dadav@users.noreply.github.com'
+ __version__ = '2.0.0'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin automatically uploads collected wifis to wigle.net'
- if READY:
- config = agent.config()
- display = agent.view()
- reported = REPORT.data_field_or('reported', default=list())
+ def __init__(self):
+ self.ready = False
+ self.report = StatusFile('/root/.wigle_uploads', data_format='json')
+ self.skip = 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')]
- new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
+ 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.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
+ return
+ self.ready = True
- if new_gps_files:
- logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
+ def on_internet_available(self, agent):
+ from scapy.all import Scapy_Exception
+ """
+ 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())
- csv_entries = list()
- no_err_entries = 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')]
+ new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
- for gps_file in new_gps_files:
- pcap_filename = gps_file.replace('.gps.json', '.pcap')
+ if new_gps_files:
+ logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
- if not os.path.exists(pcap_filename):
- logging.error("WIGLE: Can't find pcap for %s", gps_file)
- SKIP.append(gps_file)
- continue
+ csv_entries = list()
+ no_err_entries = list()
- try:
- gps_data = _extract_gps_data(gps_file)
- except OSError as os_err:
- logging.error("WIGLE: %s", os_err)
- SKIP.append(gps_file)
- continue
- except json.JSONDecodeError as json_err:
- logging.error("WIGLE: %s", json_err)
- SKIP.append(gps_file)
- continue
+ for gps_file in new_gps_files:
+ pcap_filename = gps_file.replace('.gps.json', '.pcap')
- if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
- logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
- SKIP.append(gps_file)
- continue
+ if not os.path.exists(pcap_filename):
+ logging.error("WIGLE: Can't find pcap for %s", gps_file)
+ self.skip.append(gps_file)
+ continue
+ try:
+ gps_data = _extract_gps_data(gps_file)
+ except OSError as os_err:
+ logging.error("WIGLE: %s", os_err)
+ self.skip.append(gps_file)
+ continue
+ except json.JSONDecodeError as json_err:
+ logging.error("WIGLE: %s", json_err)
+ 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.error("WIGLE: Could not extract all information. Skip %s", gps_file)
- SKIP.append(gps_file)
- continue
- except Scapy_Exception as sc_e:
- logging.error("WIGLE: %s", sc_e)
- SKIP.append(gps_file)
- continue
+ if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
+ logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
+ 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)
+ try:
+ pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
+ WifiInfo.ESSID,
+ WifiInfo.ENCRYPTION,
+ WifiInfo.CHANNEL,
+ WifiInfo.RSSI])
+ except FieldNotFoundError:
+ logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
+ self.skip.append(gps_file)
+ continue
+ except Scapy_Exception as sc_e:
+ logging.error("WIGLE: %s", sc_e)
+ self.skip.append(gps_file)
+ continue
- if csv_entries:
- display.set('status', "Uploading gps-data to wigle.net ...")
- display.update(force=True)
- try:
- _send_to_wigle(csv_entries, OPTIONS['api_key'])
- reported += no_err_entries
- REPORT.update(data={'reported': reported})
- logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
- except requests.exceptions.RequestException as re_e:
- SKIP += no_err_entries
- logging.error("WIGLE: Got an exception while uploading %s", re_e)
- except OSError as os_e:
- SKIP += no_err_entries
- logging.error("WIGLE: Got the following error: %s", os_e)
+ 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("WIGLE: Successfully uploaded %d files", len(no_err_entries))
+ except requests.exceptions.RequestException as re_e:
+ self.skip += no_err_entries
+ logging.error("WIGLE: Got an exception while uploading %s", re_e)
+ except OSError as os_e:
+ self.skip += no_err_entries
+ logging.error("WIGLE: Got the following error: %s", os_e)
diff --git a/pwnagotchi/plugins/default/wpa-sec.py b/pwnagotchi/plugins/default/wpa-sec.py
index 5b13f315..ee99f105 100644
--- a/pwnagotchi/plugins/default/wpa-sec.py
+++ b/pwnagotchi/plugins/default/wpa-sec.py
@@ -1,87 +1,84 @@
-__author__ = '33197631+dadav@users.noreply.github.com'
-__version__ = '2.0.1'
-__name__ = 'wpa-sec'
-__license__ = 'GPL3'
-__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
-
import os
import logging
import requests
from pwnagotchi.utils import StatusFile
-
-READY = False
-REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
-OPTIONS = dict()
-SKIP = list()
+import pwnagotchi.plugins as plugins
-def on_loaded():
- """
- Gets called when the plugin gets loaded
- """
- global READY
+class WpaSec(plugins.Plugin):
+ __author__ = '33197631+dadav@users.noreply.github.com'
+ __version__ = '2.0.1'
+ __license__ = 'GPL3'
+ __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
- if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
- logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
- return
+ def __init__(self):
+ self.ready = False
+ self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
+ self.options = dict()
+ self.skip = list()
- if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None):
- logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
- return
-
- READY = True
+ 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 _upload_to_wpasec(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': OPTIONS['api_key']}
- payload = {'file': file_to_upload}
+ 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
- try:
- result = requests.post(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
+ 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(agent):
- """
- Called in manual mode when there's internet connectivity
- """
- global REPORT
- global SKIP
- if READY:
- config = agent.config()
- display = agent.view()
- reported = REPORT.data_field_or('reported', default=list())
+ 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(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:
- _upload_to_wpasec(handshake)
- reported.append(handshake)
- REPORT.update(data={'reported': reported})
- logging.info("WPA_SEC: Successfully uploaded %s", handshake)
- except requests.exceptions.RequestException as req_e:
- SKIP.append(handshake)
- logging.error("WPA_SEC: %s", req_e)
- continue
- except OSError as os_e:
- logging.error("WPA_SEC: %s", os_e)
- continue
+ 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
From 66dc03ec05c6adfcaa980e5edd2343e6967cf5b0 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 14:25:20 +0100
Subject: [PATCH 159/168] Fix debug msg to fit new plugin class
---
bin/pwnagotchi | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bin/pwnagotchi b/bin/pwnagotchi
index e1409245..73f5f996 100755
--- a/bin/pwnagotchi
+++ b/bin/pwnagotchi
@@ -56,7 +56,7 @@ if __name__ == '__main__':
logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False))
for _, plugin in plugins.loaded.items():
- logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
+ logging.debug("plugin '%s' v%s" % (plugin.__class__.__name__, plugin.__version__))
if args.do_clear:
logging.info("clearing the display ...")
From 31a89cbe4b91459fc17ab7750c54d629d2cfd598 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Fri, 1 Nov 2019 15:53:31 +0100
Subject: [PATCH 160/168] new: added new angry state (closes #486)
---
pwnagotchi/automata.py | 27 ++++++++++++++++++++++-----
pwnagotchi/defaults.yml | 1 +
pwnagotchi/ui/faces.py | 1 +
pwnagotchi/ui/view.py | 5 +++++
pwnagotchi/voice.py | 7 +++++++
5 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/pwnagotchi/automata.py b/pwnagotchi/automata.py
index ee703dc2..d4e008fe 100644
--- a/pwnagotchi/automata.py
+++ b/pwnagotchi/automata.py
@@ -75,6 +75,15 @@ class Automata(object):
logging.info("unit is grateful instead of sad")
self.set_grateful()
+ 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)
+ self._view.on_angry()
+ plugins.on('angry', self)
+ else:
+ logging.info("unit is grateful instead of angry")
+ self.set_grateful()
+
def set_excited(self):
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
self._view.on_excited()
@@ -103,13 +112,21 @@ class Automata(object):
self._epoch.next()
- # after X misses during an epoch, set the status to lonely
+ # after X misses during an epoch, set the status to lonely or angry
if was_stale:
- logging.warning("agent missed %d interactions -> lonely" % did_miss)
- self.set_lonely()
- # after X times being bored, the status is set to sad
+ factor = did_miss / self._config['personality']['max_misses_for_recon']
+ if factor >= 2.0:
+ self.set_angry(factor)
+ else:
+ 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']:
- self.set_sad()
+ factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
+ if factor >= 2.0:
+ self.set_angry(factor)
+ else:
+ self.set_sad()
# after X times being inactive, the status is set to bored
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
self.set_bored()
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index bb78d8ad..4624e682 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -199,6 +199,7 @@ ui:
smart: '(✜‿‿✜)'
lonely: '(ب__ب)'
sad: '(╥☁╥ )'
+ angry: "(-_-')"
friend: '(♥‿‿♥)'
broken: '(☓‿‿☓)'
debug: '(#__#)'
diff --git a/pwnagotchi/ui/faces.py b/pwnagotchi/ui/faces.py
index f94eb0b7..c47a5e9a 100644
--- a/pwnagotchi/ui/faces.py
+++ b/pwnagotchi/ui/faces.py
@@ -16,6 +16,7 @@ DEMOTIVATED = '(≖__≖)'
SMART = '(✜‿‿✜)'
LONELY = '(ب__ب)'
SAD = '(╥☁╥ )'
+ANGRY = "(-_-')"
FRIEND = '(♥‿‿♥)'
BROKEN = '(☓‿‿☓)'
DEBUG = '(#__#)'
diff --git a/pwnagotchi/ui/view.py b/pwnagotchi/ui/view.py
index 3a4358e1..71cd46b1 100644
--- a/pwnagotchi/ui/view.py
+++ b/pwnagotchi/ui/view.py
@@ -284,6 +284,11 @@ class View(object):
self.set('status', self._voice.on_sad())
self.update()
+ def on_angry(self):
+ self.set('face', faces.ANGRY)
+ self.set('status', self._voice.on_angry())
+ self.update()
+
def on_motivated(self, reward):
self.set('face', faces.MOTIVATED)
self.set('status', self._voice.on_motivated(reward))
diff --git a/pwnagotchi/voice.py b/pwnagotchi/voice.py
index 0d06b6c3..b1abd8a5 100644
--- a/pwnagotchi/voice.py
+++ b/pwnagotchi/voice.py
@@ -67,6 +67,13 @@ class Voice:
self._('I\'m sad'),
'...'])
+ def on_angry(self):
+ # passive aggressive or not? :D
+ return random.choice([
+ '...',
+ self._('Leave me alone ...'),
+ self._('I\'m mad at you!')])
+
def on_excited(self):
return random.choice([
self._('I\'m living the life!'),
From 22e76f956cbe5e532d00c78072fd46e5198ccae6 Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Fri, 1 Nov 2019 17:45:53 +0100
Subject: [PATCH 161/168] fix: fixed memtemp for waveshare v2
---
pwnagotchi/plugins/default/memtemp.py | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py
index 521aa655..289fbabb 100644
--- a/pwnagotchi/plugins/default/memtemp.py
+++ b/pwnagotchi/plugins/default/memtemp.py
@@ -41,13 +41,23 @@ class MemTemp(plugins.Plugin):
return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(self, ui):
+ # self._layout['width'] = 250
+ # self._layout['height'] = 122
+
+ if ui.is_waveshare_v2():
+ h_pos = (180, 76)
+ v_pos = (180, 61)
+ else:
+ h_pos = (155, 76)
+ v_pos = (180, 61)
+
if self.options['orientation'] == "horizontal":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
- position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
+ position=h_pos,
label_font=fonts.Small, text_font=fonts.Small))
elif self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
- position=(ui.width() / 2 + 55, ui.height() / 2),
+ position=v_pos,
label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(self, ui):
From 4b74de48bf7c40199fb8c282d2cf99fe68ac932b Mon Sep 17 00:00:00 2001
From: Simone Margaritelli
Date: Fri, 1 Nov 2019 17:49:50 +0100
Subject: [PATCH 162/168] misc: small fix or general refactoring i did not
bother commenting
---
pwnagotchi/plugins/default/memtemp.py | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/pwnagotchi/plugins/default/memtemp.py b/pwnagotchi/plugins/default/memtemp.py
index 289fbabb..9ac2571e 100644
--- a/pwnagotchi/plugins/default/memtemp.py
+++ b/pwnagotchi/plugins/default/memtemp.py
@@ -41,11 +41,8 @@ class MemTemp(plugins.Plugin):
return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(self, ui):
- # self._layout['width'] = 250
- # self._layout['height'] = 122
-
if ui.is_waveshare_v2():
- h_pos = (180, 76)
+ h_pos = (180, 80)
v_pos = (180, 61)
else:
h_pos = (155, 76)
From 5b66d687c44dfc209b103702f0cf977c0e6f4679 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 18:00:07 +0100
Subject: [PATCH 163/168] Add multi bt-tether support
---
pwnagotchi/defaults.yml | 26 ++-
pwnagotchi/plugins/default/bt-tether.py | 218 ++++++++++++++----------
2 files changed, 146 insertions(+), 98 deletions(-)
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index bb78d8ad..5de1137b 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -71,11 +71,27 @@ main:
enabled: false
bt-tether:
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
- mac: ~ # mac of your bluetooth device
- ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
- netmask: 24
- interval: 1 # check every x minutes for device
- share_internet: false
+ devices:
+ android-phone:
+ search_order: 1 # search for this first
+ mac: ~ # mac of your bluetooth device
+ ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
+ netmask: 24
+ interval: 1 # check every minute for device
+ scantime: 10 # search for 10 seconds
+ max_tries: 10 # after 10 tries of "not found"; don't try anymore
+ share_internet: false
+ priority: 1 # low routing priority; ios (prio: 999) would win here
+ ios-phone:
+ search_order: 2 # search for this second
+ mac: ~ # mac of your bluetooth device
+ ip: '172.20.10.2' # ip from which your pwnagotchi should be reachable
+ netmask: 24
+ interval: 5 # check every 5 minutes for device
+ scantime: 20
+ max_tries: 0 # infinity
+ share_internet: false
+ priority: 999 # routing priority
memtemp: # Display memory usage, cpu load and cpu temperature on screen
enabled: false
orientation: horizontal # horizontal/vertical
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 7c89387f..2115c72e 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -149,24 +149,6 @@ class BTNap:
return None
- def is_connected(self):
- """
- Check if already connected
- """
- logging.debug("BT-TETHER: Checking if device is connected.")
-
- bt_dev = self.power(True)
-
- if not bt_dev:
- logging.debug("BT-TETHER: No bluetooth device found.")
- return None, False
-
- try:
- dev_remote = BTNap.find_device(self._mac, bt_dev)
- return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected'))
- except BTError:
- logging.debug("BT-TETHER: Device is not connected.")
- return None, False
def is_paired(self):
"""
@@ -388,8 +370,8 @@ class IfaceWrapper:
return False
@staticmethod
- def set_route(addr):
- process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
+ def set_route(gateway, device):
+ process = subprocess.Popen(f"ip route replace default via {gateway} dev {device}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
@@ -399,6 +381,39 @@ class IfaceWrapper:
return True
+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 BTTether(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
@@ -407,15 +422,33 @@ class BTTether(plugins.Plugin):
def __init__(self):
self.ready = False
- self.interval = StatusFile('/root/.bt-tether')
- self.network = None
+ self.options = dict()
+ self.devices = dict()
def on_loaded(self):
- for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
- if opt not in self.options or (opt in self.options and self.options[opt] is None):
- logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
- return
+ if 'devices' in self.options:
+ for device, options in self.options['devices'].items():
+ for device_opt in ['priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']:
+ if device_opt not in options or (device_opt in options and options[device_opt] is None):
+ logging.error("BT-TET: Pleace specify the %s for device %s.", device_opt, device)
+ break
+ else:
+ self.devices[device] = Device(name=device, **options)
+ elif 'mac' in self.options:
+ # legacy
+ for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
+ if opt not in self.options or (opt in self.options and self.options[opt] is None):
+ logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
+ return
+ self.devices['default'] = Device(name='default', **self.options)
+ else:
+ logging.error("BT-TET: No configuration found")
+ return
+
+ if not self.devices:
+ logging.error("BT-TET: No valid devices found")
+ return
# ensure bluetooth is running
bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active():
@@ -423,7 +456,6 @@ class BTTether(plugins.Plugin):
logging.error("BT-TET: Can't start bluetooth.service")
return
- self.interval.update()
self.ready = True
def on_ui_setup(self, ui):
@@ -434,88 +466,88 @@ class BTTether(plugins.Plugin):
if not self.ready:
return
- if self.interval.newer_then_minutes(self.options['interval']):
- return
+ devices_to_try = list()
+ connected_priorities = list()
+ any_device_connected = False # if this is true, last status on screen should be C
- self.interval.update()
+ for _, device in self.devices.items():
+ if device.connected():
+ connected_priorities.append(device.priority)
+ any_device_connected = True
+ continue
- bt = BTNap(self.options['mac'])
+ if not device.max_tries or (device.max_tries > device.tries):
+ if device.status.newer_then_minutes(device.interval):
+ devices_to_try.append(device)
+ device.status.update()
+ device.tries += 1
- logging.debug('BT-TETHER: Check if already connected and paired')
- dev_remote, connected = bt.is_connected()
+ sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
- if connected:
- logging.debug('BT-TETHER: Already connected.')
- ui.set('bluetooth', 'C')
- return
+ for device in sorted_devices:
+ bt = BTNap(device.mac)
- try:
- logging.info('BT-TETHER: Search device ...')
- dev_remote = bt.wait_for_device()
- if dev_remote is None:
- logging.info('BT-TETHER: Could not find device.')
+ try:
+ logging.info('BT-TETHER: Search %d secs for %s ...', device.scantime, device.name)
+ dev_remote = bt.wait_for_device(timeout=device.scantime)
+ if dev_remote is None:
+ logging.info('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
+ ui.set('bluetooth', 'NF')
+ continue
+ except Exception as bt_ex:
+ logging.error(bt_ex)
ui.set('bluetooth', 'NF')
- return
- except Exception as bt_ex:
- logging.error(bt_ex)
- ui.set('bluetooth', 'NF')
- return
+ continue
- paired = bt.is_paired()
- if not paired:
- if BTNap.pair(dev_remote):
- logging.info('BT-TETHER: Paired with device.')
+ paired = bt.is_paired()
+ if not paired:
+ if BTNap.pair(dev_remote):
+ logging.info('BT-TETHER: Paired with %s.', device.name)
+ else:
+ logging.info('BT-TETHER: Pairing with %s failed ...', device.name)
+ ui.set('bluetooth', 'PE')
+ continue
else:
- logging.info('BT-TETHER: Pairing failed ...')
- ui.set('bluetooth', 'PE')
- return
- else:
- logging.debug('BT-TETHER: Already paired.')
+ logging.debug('BT-TETHER: Already paired.')
- btnap_iface = IfaceWrapper('bnep0')
- logging.debug('BT-TETHER: Check interface')
- if not btnap_iface.exists():
- # connected and paired but not napping
- logging.debug('BT-TETHER: Try to connect to nap ...')
- network, status = BTNap.nap(dev_remote)
- self.network = network
- if status:
- logging.info('BT-TETHER: Napping!')
+
+ logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name)
+ device.network, success = BTNap.nap(dev_remote)
+
+ if success:
+ logging.info('BT-TETHER: Created interface (%s)', device.interface())
ui.set('bluetooth', 'C')
- time.sleep(5)
+ any_device_connected = True
+ device.tries = 0 # reset tries
else:
- logging.info('BT-TETHER: Napping failed ...')
+ logging.info('BT-TETHER: Could not establish nap connection with %s', device.name)
ui.set('bluetooth', 'NF')
- return
+ continue
- if btnap_iface.exists():
- logging.debug('BT-TETHER: Interface found')
+ interface = device.interface()
+ addr = f"{device.ip}/{device.netmask}"
+ gateway = ".".join(device.ip.split('.')[:-1] + ['1'])
- # check ip
- addr = f"{self.options['ip']}/{self.options['netmask']}"
-
- logging.debug('BT-TETHER: Try to set ADDR to interface')
- if not btnap_iface.set_addr(addr):
+ wrapped_interface = IfaceWrapper(interface)
+ logging.debug('BT-TETHER: Add ip to %s', interface)
+ if not wrapped_interface.set_addr(addr):
ui.set('bluetooth', 'AE')
- logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
- return
+ logging.error("BT-TETHER: Could not add ip to %s", interface)
+ continue
- logging.debug('BT-TETHER: Set ADDR to interface')
+ if device.share_internet:
+ if not connected_priorities or device.priority > max(connected_priorities):
+ logging.debug('BT-TETHER: Set default route to %s via %s', gateway, interface)
+ IfaceWrapper.set_route(gateway, interface)
+ connected_priorities.append(device.priority)
- # change route if sharking
- if self.options['share_internet']:
- logging.debug('BT-TETHER: Set routing and change resolv.conf')
- IfaceWrapper.set_route(
- ".".join(self.options['ip'].split('.')[:-1] + ['1'])) # im not proud about that
- # fix resolv.conf; dns over https ftw!
- with open('/etc/resolv.conf', 'r+') as resolv:
- nameserver = resolv.read()
- if 'nameserver 9.9.9.9' not in nameserver:
- logging.info('BT-TETHER: Added nameserver')
- resolv.seek(0)
- resolv.write(nameserver + 'nameserver 9.9.9.9\n')
+ logging.debug('BT-TETHER: Change resolv.conf if necessary ...')
+ with open('/etc/resolv.conf', 'r+') as resolv:
+ nameserver = resolv.read()
+ if 'nameserver 9.9.9.9' not in nameserver:
+ logging.info('BT-TETHER: Added nameserver')
+ resolv.seek(0)
+ resolv.write(nameserver + 'nameserver 9.9.9.9\n')
+ if any_device_connected:
ui.set('bluetooth', 'C')
- else:
- logging.error('BT-TETHER: bnep0 not found')
- ui.set('bluetooth', 'BE')
From 53ae8ea1cf38c908e6d62c73e6c09d81ffaa2e33 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 18:13:38 +0100
Subject: [PATCH 164/168] Add check if connected but no interface created
---
pwnagotchi/plugins/default/bt-tether.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 2115c72e..52306bd3 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -515,6 +515,11 @@ class BTTether(plugins.Plugin):
device.network, success = BTNap.nap(dev_remote)
if success:
+ if device.interface() is None:
+ ui.set('bluetooth', 'BE')
+ logging.info('BT-TETHER: Could not establish nap connection with %s', device.name)
+ continue
+
logging.info('BT-TETHER: Created interface (%s)', device.interface())
ui.set('bluetooth', 'C')
any_device_connected = True
From 9b58fed8621e48c8362cba68095c93bc10f70912 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 18:23:55 +0100
Subject: [PATCH 165/168] Typo
---
pwnagotchi/plugins/default/bt-tether.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 52306bd3..5ce0d0db 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -477,7 +477,7 @@ class BTTether(plugins.Plugin):
continue
if not device.max_tries or (device.max_tries > device.tries):
- if device.status.newer_then_minutes(device.interval):
+ if not device.status.newer_then_minutes(device.interval):
devices_to_try.append(device)
device.status.update()
device.tries += 1
From a2bb66ad57d54bc7380e9edd90d3df1abaffbc42 Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 20:12:04 +0100
Subject: [PATCH 166/168] Fix case
---
pwnagotchi/plugins/default/bt-tether.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 5ce0d0db..4665fb80 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -122,7 +122,7 @@ class BTNap:
device = ifaces.get(BTNap.IFACE_DEV)
if device is None:
continue
- if str(device['Address']) == device_address and path.startswith(path_prefix):
+ if str(device['Address']).lower() == device_address.lower() and path.startswith(path_prefix):
obj = bus.get_object(BTNap.IFACE_BASE, path)
return dbus.Interface(obj, BTNap.IFACE_DEV)
raise BTError('Bluetooth device not found')
From fd506b15335a622dbaaa18e7adbea4308f578c1c Mon Sep 17 00:00:00 2001
From: dadav <33197631+dadav@users.noreply.github.com>
Date: Fri, 1 Nov 2019 21:42:02 +0100
Subject: [PATCH 167/168] Fix bt-tether config
---
pwnagotchi/defaults.yml | 4 +++-
pwnagotchi/plugins/default/bt-tether.py | 17 +++++++++--------
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/pwnagotchi/defaults.yml b/pwnagotchi/defaults.yml
index 501211d3..0d7f9f7f 100644
--- a/pwnagotchi/defaults.yml
+++ b/pwnagotchi/defaults.yml
@@ -73,6 +73,7 @@ main:
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
devices:
android-phone:
+ enabled: false
search_order: 1 # search for this first
mac: ~ # mac of your bluetooth device
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
@@ -83,9 +84,10 @@ main:
share_internet: false
priority: 1 # low routing priority; ios (prio: 999) would win here
ios-phone:
+ enabled: false
search_order: 2 # search for this second
mac: ~ # mac of your bluetooth device
- ip: '172.20.10.2' # ip from which your pwnagotchi should be reachable
+ ip: '172.20.10.6' # ip from which your pwnagotchi should be reachable
netmask: 24
interval: 5 # check every 5 minutes for device
scantime: 20
diff --git a/pwnagotchi/plugins/default/bt-tether.py b/pwnagotchi/plugins/default/bt-tether.py
index 4665fb80..36e98166 100644
--- a/pwnagotchi/plugins/default/bt-tether.py
+++ b/pwnagotchi/plugins/default/bt-tether.py
@@ -426,29 +426,30 @@ class BTTether(plugins.Plugin):
self.devices = dict()
def on_loaded(self):
+ # new config
if 'devices' in self.options:
for device, options in self.options['devices'].items():
- for device_opt in ['priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']:
+ for device_opt in ['enabled', 'priority', 'scantime', 'search_order', 'max_tries', 'share_internet', 'mac', 'ip', 'netmask', 'interval']:
if device_opt not in options or (device_opt in options and options[device_opt] is None):
logging.error("BT-TET: Pleace specify the %s for device %s.", device_opt, device)
break
else:
- self.devices[device] = Device(name=device, **options)
- elif 'mac' in self.options:
- # legacy
+ if options['enabled']:
+ self.devices[device] = Device(name=device, **options)
+
+ # legacy
+ if 'mac' in self.options:
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
if opt not in self.options or (opt in self.options and self.options[opt] is None):
logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
return
- self.devices['default'] = Device(name='default', **self.options)
- else:
- logging.error("BT-TET: No configuration found")
- return
+ self.devices['legacy'] = Device(name='legacy', **self.options)
if not self.devices:
logging.error("BT-TET: No valid devices found")
return
+
# ensure bluetooth is running
bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active():
From bc1db7ceea0082a9d7f195ad70d2a7ad146cd61f Mon Sep 17 00:00:00 2001
From: LuckyFish <511225068@qq.com>
Date: Sat, 2 Nov 2019 09:42:54 +0800
Subject: [PATCH 168/168] Add files via upload
---
pwnagotchi/locale/ch/LC_MESSAGES/voice.mo | Bin 0 -> 3641 bytes
pwnagotchi/locale/ch/LC_MESSAGES/voice.po | 225 ++++++++++++++++++++++
2 files changed, 225 insertions(+)
create mode 100644 pwnagotchi/locale/ch/LC_MESSAGES/voice.mo
create mode 100644 pwnagotchi/locale/ch/LC_MESSAGES/voice.po
diff --git a/pwnagotchi/locale/ch/LC_MESSAGES/voice.mo b/pwnagotchi/locale/ch/LC_MESSAGES/voice.mo
new file mode 100644
index 0000000000000000000000000000000000000000..1ba067bcdb1f9aaa6e23f9c1be1f6ceeae6b4117
GIT binary patch
literal 3641
zcmZ9OZE#dq8OIMwtzB(ZTD8`fo{O~<$u65rQH)qhDMH$WBBW(>oY9-zlih1}?@8~y
zOIS0RNrWUD5)uNTkPs+fLI5EN$V=Ewh@W)E8K-{mvyRT5ySt7vPCq#6h%^45dv8eO
zPR{=BJulCBp8xZlGx_71)h8LA8niE=U0cQ2c5ur_@q=gAYQ~-b>%h-~`@r>}0e%iV
z0lolU1!?^U;3vSpfi(YL;HSZ-9%Jlz@OhBtZ3Q{_L-1*^16%_RgHM7J;OD`c0e=fV
zj_<#KWbd!wi{O7iTDSINzCWA5XYgGQJ`3s~+4(6*>yLvPI2QEJgXGU2Kn{KgZUEO{
zGWqceNdDD>H1EeC2RlHD{~Sp6Z-X@Nci?m2pTQ9L0Q?&G_{SN86f6wFWmW@XE4CTL
z6t*?^-Wh!F1}Uz0z^6b1)K)Qe5Tt#_kQCDQ2KX}g8<6~X0CMmdZ2m=X6G(A<4y8Kk`b
z0QBsDl!q>m_8kXFw>gmJe*jX6|0CeP1O7MQDu_PW-WM?gibUb{%jJFMF
z>(O2adSS|oh2~QX8TEly)N{OlN6?7#C#*|X+74#b_!Eien*SQ+EqL*xd~fN#xl8VZsw6l
zgzc0v&YLYmz$(`*!FAr061ELrG=I=E5=n<9E_w0L13Sca>9H1$z4@M$ZVE?ZyF@C5
z5KKKSIyAo9hzSZQEri3fG84ggHVBZXgtN}(O=4+#EHE
zhP?@X7uNV$v>hZxnozIaQy?LEv$P<5o1SXXSiO`jv{bib#*Fiq+HDcDJM8tWL63_S
zg%Bhy#2O6S7IBR=WO>Xo(t;bd@11Psv5aL%+iDIhq3`f
z2ouRq8O=gtZzt2T%NIC^5m$xOg_;HDyGkl~|rr%W;-XVT({hUBiy$0IDO=&~|A$D4B3$wiP})IH?;^Vutx`tkEr7
zIN!-Q&Ed_<^T^d^VTE^?F&Rh2)$+|vh7)4Nd|ut1bVu{$LksHeO?7NW9l0D~?)9Im
z@yq4KbMD}z%FPD1tD3l4>AqXc_f&B=H(V@SbB~b^Hy8arGX7%cT&S-xo1
ze5ab~cSj1Uw-AhZH?7K@QH8vk>8Tu_EY4l8;^mpUs(;aa?|NzEHr-q9cwceus+wGI
z$NSY#znZ<_o}MXP8ggexm^(VAru)?N+48Nkn0fSMd64|_-59#y_VufQ?hu2QHt92FhYkOa6=uN@=BeS4qm}(#6cP_A}aJ1=O^$A
z1N4Ew{BN?hLj2;~(aLxyq$zieloo~&2(2t!D-}l7^dY1G1~rCy@RbcN)Z8W7vNvBjb-8?d7JqP6W97w#GIT$4Mh#!~7cAe-#r(vQITVjCLx`!|
z82&0Fqvge33>W7*DRDFRDAwsio=W4LYGR6tZ9WfS{L&yDCVMGTH9O??Tv>Tpm+lW@
z7z+Wxp*2=c%}C!4iDYsw4hixf^8tPpBcKh)rnqgOHNLV`8v?eU79>sDh#RaLr6oZ
zFuvjlmKR4+Hsr?0`)YUrN5mcPd?XHy!6on3RXs<`Q(b;NGkB=(cdv}!mlheW;Fu2<
z)QPK=-T`Qft6QBIRKuMVLU4JOE*0Q41`)TPI~?(a^OPv>pR5(-4;_N5oYHqE=XT93
jAAI-J9Y#&%dc0PeI?kvC+aQ%F5m@yEdnD(, 2019.
+# 还有很多未翻译和翻译不准确,后期希望大家加入进来一起翻译!
+# 翻译可以联系QQ群:959559103 找 名字叫 初九 的 管理员
+msgid ""
+msgstr ""
+"Project-Id-Version: 0.0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2019-10-23 20:56+0200\n"
+"PO-Revision-Date: 2019-11-02 10:00+0008\n"
+"Last-Translator: 极客之眼-初九 <511225068@qq.com>\n"
+"Language-Team: LANGUAGE \n"
+"Language: chinese\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 "主人,你好.我是WiFi狩猎兽..."
+
+msgid "New day, new hunt, new pwns!"
+msgstr "美好的一天,狩猎开始!"
+
+msgid "Hack the Planet!"
+msgstr "我要入侵整个地球!"
+
+msgid "AI ready."
+msgstr "人工智能已启动."
+
+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 ""
+
+msgid "I'm bored ..."
+msgstr "我无聊了..."
+
+msgid "Let's go for a walk!"
+msgstr "主人带我出门走走吧!"
+
+msgid "This is the best day of my life!"
+msgstr "这是我生命中最美好的一天!"
+
+msgid "Shitty day :/"
+msgstr "今天不开心 :/"
+
+msgid "I'm extremely bored ..."
+msgstr "主人,找点事做吧 ..."
+
+msgid "I'm very sad ..."
+msgstr "我很伤心..."
+
+msgid "I'm sad"
+msgstr "我伤心了"
+
+msgid "I'm living the life!"
+msgstr ""
+
+msgid "I pwn therefore I am."
+msgstr ""
+
+msgid "So many networks!!!"
+msgstr "哇,好多猎物!!!"
+
+msgid "I'm having so much fun!"
+msgstr "我玩的好开心!"
+
+msgid "My crime is that of curiosity ..."
+msgstr "我最大的缺点就是好奇..."
+
+#, python-brace-format
+msgid "Hello {name}! Nice to meet you."
+msgstr "你好{name}!很高兴认识你."
+
+#, python-brace-format
+msgid "Unit {name} is nearby!"
+msgstr ""
+
+#, python-brace-format
+msgid "Uhm ... goodbye {name}"
+msgstr "额 ... 再见{name}"
+
+#, python-brace-format
+msgid "{name} is gone ..."
+msgstr "{name} 它走了 ..."
+
+#, python-brace-format
+msgid "Whoops ... {name} is gone."
+msgstr "哎呀... {name} 离开了."
+
+#, python-brace-format
+msgid "{name} missed!"
+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 "我可能是天煞孤星..."
+
+msgid "Where's everybody?!"
+msgstr "朋友们都去哪里了?!"
+
+#, python-brace-format
+msgid "Napping for {secs}s ..."
+msgstr "小憩{secs}s ..."
+
+msgid "Zzzzz"
+msgstr ""
+
+#, python-brace-format
+msgid "ZzzZzzz ({secs}s)"
+msgstr ""
+
+msgid "Good night."
+msgstr "晚安宝贝."
+
+msgid "Zzz"
+msgstr ""
+
+#, python-brace-format
+msgid "Waiting for {secs}s ..."
+msgstr "等待{secs}s ..."
+
+#, python-brace-format
+msgid "Looking around ({secs}s)"
+msgstr "追踪四周猎物({secs}s)"
+
+#, python-brace-format
+msgid "Hey {what} let's be friends!"
+msgstr "嗨{what}我们做朋友吧!"
+
+#, python-brace-format
+msgid "Associating to {what}"
+msgstr "正在连接到{what}"
+
+#, python-brace-format
+msgid "Yo {what}!"
+msgstr "追踪到你了{what}!"
+
+#, python-brace-format
+msgid "Just decided that {mac} needs no WiFi!"
+msgstr "猎物{mac}不需要联网,我们给它断开!"
+
+#, python-brace-format
+msgid "Deauthenticating {mac}"
+msgstr "开始攻击猎物{mac}"
+
+#, python-brace-format
+msgid "Kickbanning {mac}!"
+msgstr "已捕获{mac}!"
+
+#, python-brace-format
+msgid "Cool, we got {num} new handshake{plural}!"
+msgstr "太酷了, 我们抓到了{num}新的猎物{plural}!"
+
+#, python-brace-format
+msgid "You have {count} new message{plural}!"
+msgstr "主人,有{count}新消息{plural}!"
+
+msgid "Ops, something went wrong ... Rebooting ..."
+msgstr "行动,额等等有点小问题... 重启ing ..."
+
+#, python-brace-format
+msgid "Kicked {num} stations\n"
+msgstr "限制了{num}个猎物\n"
+
+#, python-brace-format
+msgid "Made {num} new friends\n"
+msgstr "交了{num}新朋友\n"
+
+#, python-brace-format
+msgid "Got {num} handshakes\n"
+msgstr "捕获了{num}握手包\n"
+
+msgid "Met 1 peer"
+msgstr ""
+
+#, python-brace-format
+msgid "Met {num} peers"
+msgstr ""
+
+#, 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 ""
+
+msgid "hours"
+msgstr "时"
+
+msgid "minutes"
+msgstr "分"
+
+msgid "seconds"
+msgstr "秒"
+
+msgid "hour"
+msgstr "时"
+
+msgid "minute"
+msgstr "分"
+
+msgid "second"
+msgstr "秒"