392 Commits

Author SHA1 Message Date
Simone Margaritelli
cedcc17391 releasing v1.5.1 2020-04-14 11:16:40 +02:00
Simone Margaritelli
6478b3827d Merge pull request #847 from dadav/develop
fix build problems
2020-04-14 11:12:59 +02:00
dadav
463f4b50e4 fix build problems 2020-04-13 19:39:15 +02:00
Simone Margaritelli
793cde7147 misc: updated builder with newer bettercap version 2020-04-13 17:46:15 +02:00
Simone Margaritelli
518b9e665d Merge pull request #846 from dadav/develop
add dnsmasq
2020-04-13 17:37:35 +02:00
dadav
91ea7bdb9b add dnsmasq 2020-04-13 17:30:52 +02:00
Simone Margaritelli
3ce88f1d80 Merge pull request #845 from dadav/develop
ready to merge
2020-04-13 17:29:56 +02:00
dadav
6d45d01baf update version 2020-04-13 17:19:06 +02:00
dadav
1f2dd73976 Big update 2020-04-13 17:16:24 +02:00
Simone Margaritelli
5d8d86204a Merge pull request #843 from jacopotediosi/patch-2
Add single_files option to onlinehashcrack config
2020-04-03 15:39:28 +02:00
Jacopo Tediosi
7017e39c6d Add single_files option to onlinehashcrack config
See merged PR #821
2020-04-03 15:29:24 +02:00
Simone Margaritelli
1360c734ff Merge pull request #842 from dadav/develop
Some fixes
2020-04-03 14:49:17 +02:00
Simone Margaritelli
7c90050b17 Merge pull request #840 from Skeleton022/master
Corrected hungarian translation
2020-04-03 14:49:06 +02:00
Simone Margaritelli
9ca8aacdf6 Merge pull request #821 from jacopotediosi/patch-1
Onlinehashcrack should create .pcap.cracked files
2020-04-03 14:48:54 +02:00
dadav
d39c849daf github? you ok? 2020-04-02 23:06:39 +02:00
dadav
58bbae89c2 fix some bugs 2020-04-02 22:59:51 +02:00
dadav
0dedd0974f update 2020-04-02 20:10:07 +02:00
dadav
5bac678771 typo 2020-04-02 19:30:22 +02:00
dadav
3b9aacdd16 faces use dejavu 2020-04-02 19:24:52 +02:00
dadav
305f837486 more fonts 2020-04-02 19:06:28 +02:00
dadav
54ffbbcb0b used @k0uj1k's translation 2020-04-02 18:08:30 +02:00
dadav
60167fb8fe use latest version 2020-04-01 18:21:44 +02:00
dadav
9a1565813c cant import 2020-04-01 18:21:12 +02:00
dadav
03c014f414 fix webcfg 2020-04-01 18:20:12 +02:00
dadav
d10bf6bf1d we dont want this in the repo 2020-04-01 09:40:22 +02:00
dadav
9a22321799 save in dotted format 2020-04-01 09:39:32 +02:00
dadav
76b71f5c3f fixes import error 2020-03-31 18:56:15 +02:00
dadav
4aa05bb834 /proc/stat contains the cpu ticks since boot 2020-03-31 18:56:15 +02:00
dadav
71c4458858 not needed 2020-03-31 18:56:15 +02:00
Simone Margaritelli
8260b41bab Merge pull request #813 from silsha/fix-gps
Prevent saving gps file without coordinates
2020-03-31 16:02:44 +02:00
Simone Margaritelli
86530d4b97 Merge pull request #815 from xBelladonna/bugfix/ui
Fix hardware display startup sequence
2020-03-31 16:02:26 +02:00
Simone Margaritelli
c7d9a757f6 Merge pull request #824 from xBelladonna/bugfix/journal-logging
Fix typo in systemd service to disable journal logging
2020-03-31 16:02:10 +02:00
Skeleton022
b6a0ae9d3a Added proper hungarian language support
Corrected python's bad magic number error.
2020-03-23 15:18:10 +01:00
Skeleton022
34f52b0d3a Delete voice.po 2020-03-23 15:16:11 +01:00
Skeleton022
c68cefe80d Delete voice.mo 2020-03-23 15:16:03 +01:00
Skeleton022
052c99b858 replace existing 2020-03-23 01:45:59 +01:00
Simone Margaritelli
ccea7cbeef Merge pull request #828 from davenicoll/master
Fix for gps labels on inkyphat displays
2020-03-06 13:48:23 +01:00
Simone Margaritelli
a16a5f7bcb Merge pull request #832 from Skeleton022/master
Add support for hungarian language
2020-03-06 13:48:11 +01:00
Skeleton022
a5df77d737 Add support for hungarian language 2020-03-06 07:53:47 +01:00
David Nicoll
489bce0c01 Fix for gps labels on inkyphat displays
Positioned the gps labels correctly on inkyphat displays
2020-02-26 08:53:04 +00:00
xBelladonna
da4319f81b Fix typo in systemd service
Disable journal logging

Signed-off-by: xBelladonna <isabelladonnamoore@users.noreply.github.com>
2020-02-18 05:21:40 +09:30
Jacopo Tediosi
0e1a1f4c79 Changed filename generation to a regex 2020-02-17 12:15:26 +01:00
Jacopo Tediosi
b3bdb34e3f Onlinehashcrack should create .pcap.cracked files 2020-02-17 10:55:35 +01:00
xBelladonna
c791c86bee Fix display startup sequence
Signed-off-by: xBelladonna <isabelladonnamoore@users.noreply.github.com>
2020-01-22 22:44:17 +09:30
Silsha Fux
61e5872229 Prevent saving gps file without coordinates
Signed-off-by: Silsha Fux <hallo@silsha.me>
2020-01-21 17:50:02 +01:00
Simone Margaritelli
23616095ba Merge pull request #811 from dadav/fix/disable_journal_logging
Disable logging to journald
2020-01-20 11:14:00 +01:00
Simone Margaritelli
3c8e7fbea4 Merge pull request #810 from dadav/fix/remove_test_snippet
remove accidentally commited snippet
2020-01-20 11:13:51 +01:00
Simone Margaritelli
4a5d2d36cc Merge pull request #809 from dadav/fix/ohc_typo
Fix bug in ohc plugin
2020-01-20 11:13:41 +01:00
dadav
52d432e5b6 Psssst, Lennart 2020-01-19 17:46:12 +01:00
dadav
93bdf2e3a1 remove accidentally commited snippet 2020-01-19 16:48:35 +01:00
dadav
fe97315b0f bytes... 2020-01-19 15:47:58 +01:00
Simone Margaritelli
7e80a7b9ca Merge pull request #808 from dadav/feature/ohc_download
add password download from onlinehashcrack
2020-01-19 14:46:24 +01:00
Simone Margaritelli
64385f43ed Merge pull request #806 from dadav/feature/log2ram
add log2mem functionality and refracture
2020-01-19 14:46:17 +01:00
dadav
6a4d7a895e add log2mem functionality and refracture 2020-01-19 14:44:36 +01:00
Simone Margaritelli
5606ad7281 Merge pull request #804 from xenDE/patch-9
net-pos: make api_url configurable
2020-01-19 14:18:50 +01:00
Simone Margaritelli
23f09bc4b6 Merge pull request #803 from xenDE/patch-7
webgpsmap: load extern resources over https, show current position on…
2020-01-19 14:18:43 +01:00
Simone Margaritelli
238b90d988 Merge pull request #802 from xenDE/patch-8
webgpsmap: better logging informations for easy user debugging
2020-01-19 14:18:33 +01:00
Simone Margaritelli
138316d55a Merge pull request #805 from signout/master
Trained pwnagotchi in the skill of writing danish
2020-01-19 14:17:55 +01:00
Simone Margaritelli
67bbcfef9b Merge pull request #807 from dadav/feature/session_log
add session functionality to session-stats
2020-01-19 14:17:28 +01:00
Simone Margaritelli
1a0e0c46d2 Merge pull request #800 from dadav/fix/missing_import
fix import
2020-01-19 14:16:38 +01:00
Simone Margaritelli
4cd0f46ad7 Merge pull request #752 from xBelladonna/patch-1
Small fixes: correct typos and clarify plugin log entries/default configuration
2020-01-19 14:16:19 +01:00
Simone Margaritelli
155ea54d08 Merge pull request #795 from hectorm/allow-disable-installer
Allow installer deactivation
2020-01-19 14:15:58 +01:00
Simone Margaritelli
461e53ed79 Merge pull request #799 from dadav/fix/toml_converter
Convert keys to str
2020-01-19 14:15:43 +01:00
Simone Margaritelli
6d40388002 Merge pull request #798 from dadav/fix/version_import
Fix version import
2020-01-19 14:15:35 +01:00
dadav
37b25a142f add password download 2020-01-19 11:51:11 +01:00
dadav
665ad938b4 add save_directory variable 2020-01-19 11:14:05 +01:00
dadav
301a3d99cf add session logs 2020-01-19 11:11:13 +01:00
Dennis K Jensen
c4e0acad17 Add support for danish language 2020-01-18 22:53:35 +01:00
xenDE
9339ecb2fd net-pos: make api_url configurable
now its possible tzo set an own api_url in config:

[main.plugins.net-pos]
enabled = true
api_key = "test"
api_url = "https://location.services.my-own.com/v1/geolocate?key={api}"
2020-01-18 17:35:54 +01:00
xenDE
a28c9a1176 webgpsmap: better logging informations for easy user debugging 2020-01-18 16:52:52 +01:00
xenDE
c5d6f6d362 webgpsmap: load extern resources over https, show current position on https context (for self hosted/offlinemap) 2020-01-18 16:46:35 +01:00
dadav
ff843f0367 fix import 2020-01-18 09:30:44 +01:00
xBelladonna
717cb02743 Fix English typos
Fixed a typo in English translation, needing to regenerate locales because of changed msgid
Fixed typo in onlinehashcrack plugin logging

Signed-off-by: xBelladonna <isabelladonnamoore@users.noreply.github.com>
2020-01-18 14:31:15 +09:30
xBelladonna
8be643b2e0 Announce OnlineHashCrack plugin loaded in logs
Signed-off-by: xBelladonna <isabelladonnamoore@users.noreply.github.com>
2020-01-18 14:30:27 +09:30
xBelladonna
814392daa5 Add port onto paw-gps IP in logs for clarity
We show ip:port instead of just ip in logs to avoid confusion

Signed-off-by: xBelladonna <isabelladonnamoore@users.noreply.github.com>
2020-01-18 14:30:27 +09:30
dadav
4cc1c2ac1f more compact 2020-01-17 19:15:01 +01:00
dadav
fae6a0942b Convert keys to str 2020-01-17 19:12:15 +01:00
dadav
53ab63cf8a fix import 2020-01-17 18:10:33 +01:00
Simone Margaritelli
0764304be9 Merge pull request #796 from dadav/fix/version_parsing
Fix/version parsing
2020-01-17 11:37:46 +01:00
dadav
cdc0e0fa3e adjust release script 2020-01-16 19:25:58 +01:00
dadav
5ccd65e46e fix typo 2020-01-16 19:19:58 +01:00
dadav
afc3636939 fix version parsing 2020-01-16 18:52:40 +01:00
Simone Margaritelli
f154b97ab9 Merge pull request #770 from gerard780/patch-1
added support for timezones with - offset
2020-01-16 12:02:47 +01:00
Simone Margaritelli
66acecb387 Merge pull request #784 from hectorm/add-dbus-python
Add dbus-python to requirements.txt
2020-01-16 12:02:07 +01:00
Simone Margaritelli
a31d0a5e19 Merge pull request #786 from mbgroot/master
Updating the Russian translation: mo and po files.
2020-01-16 12:01:51 +01:00
Simone Margaritelli
026b9fc513 Merge pull request #787 from dadav/fix/bt-tether-params
Bt-Tether fix
2020-01-16 12:01:40 +01:00
Simone Margaritelli
1cdc1641fc Merge pull request #788 from dadav/fix/preview_toml_adjustment
Adjust preview.py (toml)
2020-01-16 12:01:17 +01:00
Simone Margaritelli
71de5925ee Merge pull request #790 from hectorm/use-sys-exit
Use "sys.exit" instead of "exit" builtin
2020-01-16 12:00:59 +01:00
Simone Margaritelli
4733e90e77 Merge pull request #792 from hectorm/bad-values-toml
Converted back to integer some values from "defaults.toml"
2020-01-16 12:00:48 +01:00
Héctor Molinero Fernández
7cf0a2ef4b Allow installer deactivation
Signed-off-by: Héctor Molinero Fernández <hector@molinero.dev>
2020-01-15 23:36:29 +01:00
Héctor Molinero Fernández
97e03843bd Converted back to integer some values from "defaults.toml"
Signed-off-by: Héctor Molinero Fernández <hector@molinero.dev>
2020-01-14 23:11:30 +01:00
Héctor Molinero Fernández
8b078383c2 Use "sys.exit" instead of "exit" builtin
Signed-off-by: Héctor Molinero Fernández <hector@molinero.dev>
2020-01-14 22:23:01 +01:00
gerard780
3e5bece3cf Merge remote-tracking branch 'upstream/master' into patch-1 2020-01-14 16:10:30 -05:00
Héctor Molinero Fernández
7645be6f3e Merge branch 'master' of https://github.com/evilsocket/pwnagotchi into add-dbus-python 2020-01-14 19:37:05 +01:00
dadav
779da95f78 related to toml migration 2020-01-14 18:37:39 +01:00
dadav
51e13aa1ad related to toml migration 2020-01-14 18:20:00 +01:00
Evgeny Zelenin
e489678cf5 Updating the Russian translation
Updating the Russian translation - po and mo files. Locally tested.
2020-01-14 16:02:20 +05:00
Simone Margaritelli
52015014b4 Merge pull request #767 from foreign-sub/packer_version
Add PACKER_VERSION to Makefile, bump packer to 1.4.5
2020-01-14 11:39:26 +01:00
Simone Margaritelli
f691f737ab Merge pull request #747 from dadav/feature/migrate_to_toml
Switch to toml
2020-01-14 11:39:13 +01:00
Héctor Molinero Fernández
2617a6edea Add dbus-python to requirements.txt
Signed-off-by: Héctor Molinero Fernández <hector@molinero.dev>
2020-01-13 23:54:07 +01:00
gerard780
6001b7630b Merge pull request #1 from dadav/fix/date_parsing
dateutil is easier than regex
2020-01-13 15:26:38 -05:00
dadav
78fba1f74b dateutil is easier than regex 2020-01-13 20:10:43 +01:00
dadav
6075296884 Switch to toml 2020-01-13 19:20:40 +01:00
Simone Margaritelli
9457622713 Merge pull request #779 from soebbing/add-easymaxis-fixes
Small german language fixes
2020-01-13 11:42:22 +01:00
Simone Margaritelli
7ef1c1f2f0 Merge pull request #774 from dadav/fix/a2c_files
Prevents permanent tfevent files
2020-01-13 11:41:54 +01:00
Simone Margaritelli
8e0488e16f Merge pull request #771 from espinielli/patch-1
typo (maybe)
2020-01-13 11:41:40 +01:00
Simone Margaritelli
5934ac4a55 Merge pull request #759 from WheresAlice/feature/749-pycryptodome-dependency
fix incorrect dependency for Crypto
2020-01-13 11:40:37 +01:00
Simone Margaritelli
15bae093fb Merge pull request #758 from xenDE/patch-6
webgpsmap: add function for download the map as one html file and show current position on map
2020-01-13 11:40:26 +01:00
Simone Margaritelli
0c76cd7ea7 Merge pull request #757 from dadav/feature/switcher
Add switcher plugin
2020-01-13 11:40:07 +01:00
Simone Margaritelli
5834b27ed8 Merge pull request #742 from TechAdvancedCyborg/master
Updated French Translations
2020-01-13 11:39:41 +01:00
Simone Margaritelli
a8ed9bcc1c Merge pull request #741 from benallard/ups_shutdown
ups_lite: Add auto-shutdown
2020-01-13 11:39:27 +01:00
Simone Margaritelli
0e88c3aa6a Merge pull request #720 from moheshmohan/master
Added support for waveshare 2.13 B display
2020-01-13 11:39:13 +01:00
Hendrik Söbbing
b1d61d95e6 Small german language fixes
Signed-off-by: Hendrik Söbbing <h.soebbing@shopware.com>
2020-01-03 09:12:26 +01:00
dadav
215af0fc88 Prevents permanent tfevent files 2020-01-02 14:07:27 +01:00
Enrico Spinielli
c09b72ff7e typo (maybe) 2019-12-31 14:54:38 +01:00
gerard780
2f1b35b3fa added support for timezones with - offset
introduced new regexp to better handle timestamps with a negative timezone offset like '2019-21-30T14:01:46.79231-05:00'
2019-12-30 14:01:59 -05:00
foreign-sub
d435ef2ba9 Add PACKER_VERSION to Makefile, bump packer to 1.4.5
Signed-off-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
2019-12-29 22:10:22 +01:00
xenDE
4164e7c067 webgpsmap: get current position and set marker on map in interval (30s)
used the browsers geolocation function to get the current location, show a marker and center the map there.
on success (user allows usage of get-current-position) it gets position every 30s and reposition the marker without center the map to the marker.
2019-12-29 14:57:17 +01:00
WheresAlice
bb7737762c fix incorrect dependency for Crypto
Signed-off-by: WheresAlice <WheresAlice@users.noreply.github.com>
2019-12-27 18:03:50 +00:00
xenDE
c300e73726 webgpsmap: add function for download the map as one html file with json-positions inside
now it's possible to download the map as one Html file for local use on your pc or host somewhere for mobile use without your pownagotchi. uri: /plugins/webgpsmap/offlinemap
2019-12-26 18:51:00 +01:00
dadav
0587c4b09a Add switcher plugin 2019-12-26 09:43:07 +01:00
Simone Margaritelli
63dc672b11 releasing v1.4.3 2019-12-23 11:21:28 +01:00
evilsocket
0dac137df0 Merge pull request #746 from dadav/fix/scipy_version
Add scipy to requirements.txt
2019-12-23 11:16:53 +01:00
dadav
3db9ccb47e Add scipy to requirements.txt 2019-12-21 17:20:05 +01:00
T.A.C.T.I.C.A.L
f375e4905f Recompiled voice.mo
Recompiled voice.mo
2019-12-20 17:46:42 +01:00
T.A.C.T.I.C.A.L
8d17cf0bd2 Updated French Translations
Updated French translations to fix typos and translation issues...
2019-12-20 17:44:40 +01:00
Benoît Allard
d981b26842 ups_lite: Add auto-shutdown
Signed-off-by: Benoît Allard <benoit.allard@gmx.de>
2019-12-19 20:11:39 +01:00
Simone Margaritelli
a7164ea742 releasing v1.4.2 2019-12-19 17:36:09 +01:00
evilsocket
cae2a18016 Merge pull request #735 from dadav/fix/add_unload
add unload method
2019-12-17 16:48:23 +01:00
dadav
9d63eba232 add unload method 2019-12-16 18:50:40 +01:00
evilsocket
f141e15ba3 Merge pull request #727 from ArnCo/master
Set correct position for memtemp plugin so that it does not overlap with regular messages for waveshare27inch.
2019-12-16 12:00:46 +02:00
evilsocket
e68165ce06 Merge pull request #732 from xenDE/patch-5
webgpsmap: fix parsing new timezone format "Z" in gps data
2019-12-16 12:00:17 +02:00
evilsocket
3758806919 Merge pull request #725 from dadav/fix/remove_sysd_process_limits
remove process limits
2019-12-16 11:59:59 +02:00
evilsocket
1f91c6f09e Merge pull request #724 from dadav/feature/ensure_write
add ensure_write
2019-12-16 11:59:40 +02:00
evilsocket
3e8b6eafbd Merge pull request #722 from dadav/fix/no_comment
no comment
2019-12-16 11:59:12 +02:00
xenDE
44138ba463 webgpsmap: fix parsing new timezone format "Z" in gps data
fix timezone "Z": "2019-11-28T04:44:46.79231Z" >> "2019-11-28T04:44:46.79231+00:00"
issue: https://github.com/evilsocket/pwnagotchi/issues/708
2019-12-15 23:37:48 +01:00
ACO
4b71fea404 Set correct position for memtemp plugin so that it does not overlap with regular messages for waveshare27inch.
Signed-off-by: ArnCo <arnaud@cordier.work>
2019-12-14 11:49:21 +01:00
dadav
6babad0d02 remove process limits 2019-12-14 09:00:54 +01:00
dadav
e8513240ea add ensure_write 2019-12-13 19:42:04 +01:00
dadav
00101ccd07 no comment 2019-12-13 19:29:44 +01:00
mohesh.mohan
a0bc911c0e Display freeze recover enhancements - delay between poweron and off 2019-12-13 21:32:52 +04:00
mohesh.mohan
6d71bcd965 Display freeze recover enhancements 2019-12-13 21:08:56 +04:00
mohesh.mohan
91447a2a31 Waveshare213bc hung issues workaround - optimizations 2019-12-13 11:01:40 +04:00
mohesh.mohan
e06480e474 Waveshare213bc hung issues workaround 2019-12-13 10:33:41 +04:00
mohesh.mohan
819146f83a waveshare213b and waveshare213c support bug fixes 2019-12-12 01:56:52 +04:00
mohesh.mohan
cdd4c13336 waveshare213b and waveshare213c support bug fixes 2019-12-12 01:09:03 +04:00
mohesh.mohan
704d7ceaa1 waveshare213b and waveshare213c support bug fixes 2019-12-12 00:46:14 +04:00
mohesh.mohan
a4daf4af61 waveshare213b and waveshare213c support bug fixes 2019-12-12 00:37:24 +04:00
mohesh.mohan
eddcf32b62 213bc support additions 2019-12-11 23:47:14 +04:00
mohesh.mohan
6117235c52 added 213bc support 2019-12-11 23:08:29 +04:00
Simone Margaritelli
e0a66f5c99 misc: small fix or general refactoring i did not bother commenting 2019-12-10 21:22:44 +02:00
Simone Margaritelli
81061cea24 fix: fixed locked callback call on plugins 2019-12-10 21:17:46 +02:00
evilsocket
93bb633010 Merge pull request #713 from alanyee/patch-1
Replace string formatting with logging laziness in __init__.py
2019-12-10 21:06:57 +02:00
evilsocket
dbb64e0fab Merge pull request #711 from dadav/fix/bugs
Unknown variable; Logic error
2019-12-10 19:40:57 +02:00
Alan Yee
fa8751017d Update __init__.py 2019-12-09 15:36:26 -08:00
dadav
9d56c97aa5 Unknown variable; Logic error 2019-12-09 20:12:34 +01:00
Evgeny Zelenin
10f7161240 voice.mo update 2019-12-09 21:52:23 +05:00
Evgeny Zelenin
9b02548176 del old voice.mo 2019-12-09 21:52:06 +05:00
Evgeny Zelenin
f5f47c4f88 voice.mo update 2019-12-09 21:50:50 +05:00
evilsocket
1523dfc1ef Merge pull request #701 from alanyee/patch-1
Update automata.py
2019-12-09 18:10:15 +02:00
evilsocket
a02960b56d Merge pull request #705 from hmax42/master
UI adjustments for waveshare1
2019-12-09 18:09:19 +02:00
evilsocket
b6f59f99d4 Merge pull request #707 from AliceGrey/master
Added text overflow checking for over 999 associations
2019-12-09 18:08:33 +02:00
Simone Margaritelli
09a00adab9 fix: added a plugin::callback level mutex to avoid calling a callback while a previous call is still running 2019-12-09 17:46:44 +02:00
AliceGrey
7fa30c2868 Added text overflow checking for over 999 associations 2019-12-08 17:16:05 -08:00
hmax42
774d9c693c Update defaults.yml 2019-12-08 16:26:14 +01:00
hmax42
87b6cf7d40 Merge branch 'master' of https://github.com/hmax42/pwnagotchi 2019-12-08 16:25:23 +01:00
hmax42
88928eec82 remove buttonshim 2019-12-08 16:25:07 +01:00
hmax42
60f7849838 Merge branch 'master' into pr/3 2019-12-08 08:46:24 +01:00
hmax42
3cf041617c Update memtemp.py 2019-12-08 08:44:20 +01:00
evilsocket
ee6c06f306 Merge pull request #700 from dadav/fix/auto-update-lock
Add Lock
2019-12-07 16:28:57 +02:00
dadav
2e22a17610 Add Lock 2019-12-07 15:27:41 +01:00
Simone Margaritelli
1615fc8817 releasing v1.4.1 2019-12-07 15:45:15 +02:00
Simone Margaritelli
714cb00610 misc: small fix or general refactoring i did not bother commenting 2019-12-07 15:44:03 +02:00
Simone Margaritelli
a0a790635a fix: fuck me for trusting people's PR without checking 10000 times 2019-12-07 15:43:10 +02:00
Simone Margaritelli
9d17be959d releasing v1.4.0 2019-12-07 15:08:17 +02:00
evilsocket
4b651cd17b Merge pull request #694 from Evg33/fr_waveshare144lcd
waveshare 1.44inch lcd hat
2019-12-07 14:11:25 +02:00
evilsocket
2f70512076 Merge pull request #693 from Nels885/improve-plugins-web-page
improvement of the plugins web page
2019-12-07 14:10:37 +02:00
evilsocket
b79c59c639 Merge pull request #698 from dadav/feature/usr1_handler
Add signal handler
2019-12-07 14:09:36 +02:00
evilsocket
56f7b67699 Merge pull request #699 from dadav/fix/add-lock-to-ohc
Add lock to onlinehashcrack plugin
2019-12-07 14:09:05 +02:00
dadav
1a8472268e Add signal handler 2019-12-07 09:36:56 +01:00
dadav
4fb7205281 Add lock 2019-12-07 09:30:05 +01:00
Alan Yee
f8ffab426b Update automata.py
Replace string formatting with logging laziness in automata.py
2019-12-06 12:20:54 -08:00
Evg33
b4daf19401 layout redesign 2019-12-06 20:11:37 +03:00
evilsocket
e04e053cee Merge pull request #690 from alanyee/patch-1
Replace string formatting with logging laziness in agent.py
2019-12-06 15:04:45 +02:00
evilsocket
4f153e3899 Merge pull request #689 from dadav/fix/bt-tether-lock
Add lock to bt-tether
2019-12-06 15:01:29 +02:00
Evg33
04720ecc42 memtemp plugin for waveshare.com/1.44inch-lcd-hat 2019-12-06 03:24:44 +03:00
Evg33
a12e2aafa5 gps plugin for waveshare.com/1.44inch-lcd-hat 2019-12-06 03:24:24 +03:00
Evg33
1721f67ec3 fr waveshare.com/1.44inch-lcd-hat 2019-12-06 03:24:01 +03:00
Nels885
7693e42aa1 Change the name of the CSS class 'element' to 'plugins-box' and centering the text 2019-12-05 11:49:34 +01:00
Nels885
2ae48a2ef2 Changing the display style of the plugins page 2019-12-05 11:15:29 +01:00
Alan Yee
663bca41cd Update agent.py
Leverage logging laziness rather than string formatting
2019-12-03 11:34:40 -08:00
dadav
ede01e50cd Add lock 2019-12-03 18:15:35 +01:00
evilsocket
19973574e7 Merge pull request #677 from dadav/fix/plugins_setup_ui
Fix/plugins setup ui
2019-12-03 11:44:57 +01:00
evilsocket
a019b4c778 Merge pull request #678 from dadav/fix/net-pos-bug
Add lock; make less verbose
2019-12-03 11:44:12 +01:00
evilsocket
526c5bed87 Merge pull request #680 from Nels885/update-french-translation
Update french translation
2019-12-03 11:42:47 +01:00
evilsocket
4d6136633a Merge pull request #682 from jakubmilkowski/master
fix: Prevent duplicate entries for uploaded pcaps
2019-12-03 11:42:22 +01:00
evilsocket
6d90b75d10 Merge pull request #684 from dadav/feature/session_stats_plugin
Add session-stats plugin
2019-12-03 11:41:20 +01:00
evilsocket
03695be807 Merge pull request #687 from alanyee/patch-1
Use list comprehension in setup.py
2019-12-03 11:40:34 +01:00
Alan Yee
6a97476732 Update setup.py
Use list comprehension
2019-12-02 13:32:36 -08:00
dadav
b5e620684b this is apparently needed 2019-12-02 19:22:36 +01:00
dadav
95557ab37d Axes information will be lost otherwise 2019-12-02 19:20:47 +01:00
dadav
988d093e36 Add session-stats plugin 2019-12-01 21:49:27 +01:00
hmax42
f563d71477 Update gps.py 2019-12-01 14:27:13 +01:00
hmax42
7b219fd139 Update memtemp.py 2019-12-01 14:27:10 +01:00
hmax42
42ed698583 Update memtemp.py 2019-12-01 08:06:14 +01:00
hmax42
6df7bcd885 add ws1 2019-12-01 07:56:09 +01:00
hmax42
1c299832ae add ws1 2019-12-01 07:56:03 +01:00
hmax42
11476433ca Merge pull request #2 from evilsocket/master
update
2019-12-01 07:49:23 +01:00
jakubmilkowski
6e57e131b3 fix: Prevent duplicate entries for uploaded pcaps
For the same reasons like described here https://github.com/evilsocket/pwnagotchi/issues/657 duplicated entries could be created in /root/.ohc_uploads

Signed-off-by: jakubmilkowski <jakub.milkowsky@gmail.com>
2019-11-30 22:44:03 +01:00
dadav
548b42ef20 Lock ui on change 2019-11-30 14:20:37 +01:00
dadav
99614c8cd4 Call on_ready 2019-11-30 14:01:35 +01:00
VOIRIN Lionel
e19ea999e2 Correction of some French translations 2019-11-30 12:51:53 +01:00
VOIRIN Lionel
2207a1eacf Updating the French translation 2019-11-30 10:54:38 +01:00
dadav
9509dd0aa5 Add lock; make less verbose 2019-11-30 10:05:43 +01:00
dadav
608904daf8 Call unload with ui arg 2019-11-30 09:43:39 +01:00
dadav
f973997cdb Call on_ui_setup when plugin reloads 2019-11-30 09:35:19 +01:00
evilsocket
9b594f7fb2 Merge pull request #669 from cdiemel/master
added on_unfiltered_wifi_list
2019-11-29 15:45:43 +01:00
evilsocket
b8eed4f52a Merge pull request #671 from Evg33/reboot
feature/plugin/web/reboot
2019-11-29 15:45:26 +01:00
evilsocket
ad510429fe Merge pull request #672 from dadav/feature/plugins_urls
Add urls to plugins
2019-11-29 15:44:58 +01:00
dadav
f5a94fde96 Add url to plugin 2019-11-28 21:55:20 +01:00
Evg33
855bda9104 feature/plugin/web/reboot 2019-11-28 23:02:22 +03:00
Casey Diemel
e72fd08fb4 added on_unfiltered_wifi_list
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-11-28 14:39:15 -05:00
evilsocket
e44ebac43f Merge pull request #627 from soebbing/improve-german-translations
Improve German translations slightly
2019-11-28 11:29:30 +01:00
evilsocket
b5ddb716e2 Merge pull request #660 from mbgroot/master
Updating the Russian translation
2019-11-28 11:28:54 +01:00
evilsocket
82e7e09fa1 Merge pull request #664 from dadav/feature/wpa-sec-download
Add wpa-sec password download
2019-11-28 11:28:36 +01:00
evilsocket
8b40e94ca8 Merge pull request #665 from dadav/feature/plugins_web_page
Add plugins page
2019-11-28 11:28:15 +01:00
dadav
cc5c46906f Add plugins page 2019-11-27 21:22:40 +01:00
dadav
7cb52ba33a Add wpa-sec password download 2019-11-27 18:51:37 +01:00
Evgeny Zelenin
07f8e7bd4a Update voice.po 2019-11-27 22:01:43 +05:00
Evgeny Zelenin
48dc751d13 Update voice.po 2019-11-27 12:09:24 +05:00
Evgeny Zelenin
3c154ffe0c Update voice.po 2019-11-27 02:58:16 +05:00
Evgeny Zelenin
167f559d73 Update voice.po 2019-11-27 02:54:48 +05:00
evilsocket
19775b7d27 Merge pull request #654 from dadav/fix/webcfg_check_for_error
Fix/webcfg check for error
2019-11-26 12:32:10 +01:00
evilsocket
48e3a372cc Merge pull request #658 from sayak-brm/master
[BUGFIX] Prevent duplicate entries for reported networks
2019-11-26 12:04:00 +01:00
Sayak Brahmachari
d2c44797e5 Prevent duplicate entries for reported networks
Due to duplicate entries in `/root/.api-report.json`, [this code](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/plugins/default/grid.py#L90) incorrectly reports the number of pwned networks, resulting in incorrect stats on the [pwnagotchi.ai website](https://pwnagotchi.ai/).
2019-11-26 14:05:10 +05:30
dadav
a03443986b Parse to str 2019-11-25 20:08:20 +01:00
dadav
a7ea499fac Should fail before write 2019-11-25 19:47:23 +01:00
evilsocket
722a91655a Merge pull request #649 from xenDE/patch-4
webgpsmap: add filter for: SSID, MAC, isCracked, Password
2019-11-25 12:03:49 +01:00
xenDE
93e06d7f59 add filter for: SSID, MAC, isCracked, Password 2019-11-24 18:49:32 +01:00
evilsocket
7de5121033 Merge pull request #645 from xenDE/patch-3
fix gpio_buttons plugin: gpio needs to be a number
2019-11-23 11:30:28 +01:00
xenDE
83f741bbb0 fix: gpio needs to be a number
fixes gpio id as string in config
https://github.com/evilsocket/pwnagotchi/issues/643
2019-11-23 02:05:01 +01:00
evilsocket
a779fb9b0b Merge pull request #640 from xenDE/patch-2
cleanup, fixes and add handling of .paw-gps.json
2019-11-21 17:03:08 +01:00
xenDE
c4a007e72a cleanup, fixes and add handling of .paw-gps.json
cleanup logging, increase cache item count from 1024 to 2048, add handling of corrupt long/lat position null, added handling of .paw-gps.json files - tested with master
2019-11-20 15:45:16 +01:00
evilsocket
1a71615fa8 Merge pull request #635 from Arttumiro/patch-3
Update to using .paw-gps.json files
2019-11-20 10:11:16 +01:00
evilsocket
7a9f84f495 Merge pull request #638 from xslendix/master
Added romanian language
2019-11-20 10:11:06 +01:00
evilsocket
6e3f5a1181 Merge pull request #632 from daniel156161/master
fix backup.sh (find with type f for no zero byte files into archive)
2019-11-20 10:10:33 +01:00
root
d045ed5afa Added romanian language 2019-11-19 22:58:49 +00:00
Arttumiro
0ee0aaff37 Update to using .paw-gps.json files
Remove old link due to it not being too good, update file extension to .paw-gps.json for better support on problems with webgpsmap.
2019-11-19 15:38:11 +02:00
evilsocket
0fb81a11c4 Merge pull request #629 from xenDE/patch-1
fix gps iso-datetime parsing
2019-11-19 11:30:15 +01:00
daniel156161
cfc0ad1b48 fix backup.sh (find with type f for no zero byte files into archive) 2019-11-19 05:03:21 +01:00
xenDE
3351c251ef fix gps timestamp parsing
problem on timestamp parsing if microseconds are more then 6 numbers.

will fix bug reported in this pr: https://github.com/evilsocket/pwnagotchi/pull/619

tested with testdata from https://github.com/xenDE/pwnagotchi-plugin-webgpsmap/tree/master/handshakes.gps-map-test

before:

[2019-11-19 00:37:51,946] [INFO] webgpsmap: scanning /root/handshakes.gps-map-test
[2019-11-19 00:37:52,022] [INFO] webgpsmap: Found 4 .(geo|gps).json files from 5 handshakes. Fetching positions ...
[2019-11-19 00:37:52,144] [ERROR] Lng is 0
[2019-11-19 00:37:52,241] [ERROR] Invalid isoformat string: '2019-11-14T12:30:41.097414739+01:00'
[2019-11-19 00:37:52,280] [ERROR] Lng is 0
[2019-11-19 00:37:52,329] [INFO] webgpsmap loaded 2 positions

after:

[2019-11-19 00:48:04,652] [INFO] webgpsmap: scanning /root/handshakes.gps-map-test
[2019-11-19 00:48:04,693] [INFO] webgpsmap: Found 5 .(geo|gps).json files from 6 handshakes. Fetching positions ...
[2019-11-19 00:48:04,760] [ERROR] Lng is 0
[2019-11-19 00:48:04,822] [ERROR] Lng is 0
[2019-11-19 00:48:04,850] [INFO] webgpsmap loaded 3 positions
2019-11-19 00:56:59 +01:00
Hendrik Söbbing
d9d399429c Improve German translations slightly
Signed-off-by: Hendrik Söbbing <h.soebbing@shopware.com>
2019-11-18 18:31:11 +01:00
hmax42
b1ad247e11 Merge pull request #1 from evilsocket/master
pull
2019-11-18 17:19:52 +01:00
evilsocket
b5a148f287 Merge pull request #625 from neutralinsomniac/invert_led_blink
fix: apparently for the led, 0 is ON and 1 is OFF
2019-11-18 15:56:50 +01:00
Jeremy O'Brien
7138f6469b fix: apparently for the led, 0 is ON and 1 is OFF
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-11-18 08:57:10 -05:00
Simone Margaritelli
785d678e30 releasing v1.3.0 2019-11-18 14:12:13 +01:00
Simone Margaritelli
9c8784e533 fix: reload inbox every 15 seconds 2019-11-18 13:24:58 +01:00
Simone Margaritelli
fd288b4acd misc: small fix or general refactoring i did not bother commenting 2019-11-18 13:21:44 +01:00
evilsocket
b704614254 Merge pull request #623 from Arttumiro/patch-2
Add link to better guide to paw-gps.py
2019-11-18 12:29:44 +01:00
Arttumiro
2dc36651df Add link to better guide to paw-gps.py
Got permission from LeonT for this
2019-11-18 13:27:30 +02:00
evilsocket
d8d6d52eda Merge pull request #617 from andrewbeard/master
Fix for issue #616
2019-11-16 10:09:49 +01:00
Andrew Beard
43c5ab7ecf - Fix the temperature symbol when using something other than celsius
- Add defaults so we don't throw an exception if an invalid scale is
selected. Bad things happen if you spell fahrenheit wrong.
2019-11-16 01:29:02 -05:00
evilsocket
de62424dbc Merge pull request #615 from neutralinsomniac/fix_backup_script_default_username
fix: don't use $USERNAME as default username for backup/restore scripts, as this is usually defined on linux hosts as the current logged in user
2019-11-15 14:59:53 +01:00
Jeremy O'Brien
8c51936c13 fix: don't use $USERNAME as default username for backup/restore scripts, as this is usually defined on linux hosts as the current logged in user
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-11-15 07:59:32 -05:00
Simone Margaritelli
87e46610f9 fix: --clear does not start the agent anymore 2019-11-15 12:27:53 +01:00
evilsocket
a96dead519 Merge pull request #606 from Arttumiro/master
Fixed Paw-Gps config, added a - mark
2019-11-15 12:06:06 +01:00
evilsocket
8ed2950eb5 Merge pull request #607 from Arttumiro/patch-1
Fixes to paw-gps.py
2019-11-15 12:05:58 +01:00
evilsocket
6f8133b2b8 Merge pull request #608 from benleb/add-spacing-to-gps
decrease spacing in gps ui
2019-11-15 12:05:07 +01:00
evilsocket
16afa87112 Merge branch 'master' into add-spacing-to-gps 2019-11-15 12:05:00 +01:00
evilsocket
ed22343877 Merge pull request #612 from budd3993/fix-webgpsmap-negatives
webgpsmap plugin - fixed check for nonzero lat/long
2019-11-15 12:04:00 +01:00
evilsocket
c70f2c30e9 Merge pull request #613 from wystans/master
Added signal strength to logging messages
2019-11-15 12:03:39 +01:00
Wystan Schmidt
5111490c70 Added rssi info to logging messages
Signed-off-by: Wystan Schmidt <wystans@gmail.com>
2019-11-14 20:13:50 -07:00
Josh Bauer
59ae35372e fixed check for nonzero lat/long 2019-11-14 21:15:12 -05:00
evilsocket
5f593a4231 Merge pull request #609 from benleb/more-pythonic
f-strings and double quotes in gps plugin
2019-11-14 23:05:43 +01:00
Arttumiro
56cc872daa Update paw-gps.py 2019-11-14 22:29:58 +02:00
Arttumiro
6e1490da78 Fix on_handshake, whole plugin should work now
Add the else: so if the ip is set in the options the plugin actually works.
2019-11-14 22:29:43 +02:00
Ben Lebherz
69597103b5 use f-strings and double quotes in gps plugin 2019-11-14 15:57:43 +01:00
Ben Lebherz
96ca5dd8e3 decrease spacing in gps ui 2019-11-14 15:46:12 +01:00
evilsocket
3efa3a935a Merge pull request #601 from benleb/add-gateway-option
add gateway option to bt-tether
2019-11-14 11:04:30 +01:00
evilsocket
39ccd141eb Merge pull request #602 from benleb/add-gps-coords-to-ui
show gps coords of last handshake in ui
2019-11-14 11:04:12 +01:00
evilsocket
1a30b52a90 Merge pull request #605 from benleb/make-label-spacing-dynamic
make label to value space configurable to better fit small fonts
2019-11-14 11:03:28 +01:00
Arttumiro
8965ad9272 change default ip of paw-gps, it needs port too.
Change default ip from 192.168.44.1 (which is the ip address of an android on bt tether) to 192.168.44.1:8080 so it has the default port paw-gps opens on, which is 8080.
2019-11-14 11:45:39 +02:00
Arttumiro
5dae0ce982 Fixed Paw-Gps, added a - mark to the config.
Before, paw-gps was set as pawgps in the config, this was different than the filename (paw-gps.py) so it didnt work, this request should fix that. (if this is the place that sets /etc/pwnagotchi/default.yml, this is my first PR so sorry im dumb)
2019-11-14 11:26:32 +02:00
Ben Lebherz
92266a783a make label to value space configurable to better fit small fonts 2019-11-13 21:11:21 +01:00
Ben Lebherz
9e656d4ea6 show gps coords of last handshake in ui 2019-11-13 15:38:21 +01:00
Ben Lebherz
1d255b577d add gateway option to bt-tether 2019-11-13 15:37:20 +01:00
Simone Margaritelli
1ff14c05a9 misc: small fix or general refactoring i did not bother commenting 2019-11-13 15:05:21 +01:00
Simone Margaritelli
ab63ecccd7 new: macos connection share script now autodetects the usb interface and uses en0 as default upstream (closes #597) 2019-11-13 14:54:49 +01:00
Simone Margaritelli
a7e37115d9 misc: small fix or general refactoring i did not bother commenting 2019-11-13 14:38:39 +01:00
Simone Margaritelli
b1d8aa3ba1 misc: several improvements on the web ui 2019-11-13 14:37:13 +01:00
evilsocket
6e26463278 Merge pull request #599 from ahsec/add_spanish_support
Adding support for Spanish language
2019-11-13 11:39:06 +01:00
evilsocket
7261073073 Merge pull request #600 from lexusburn/patch-1
small typo fix
2019-11-13 11:38:54 +01:00
lexusburn
a02c1d6d92 small typo fix 2019-11-13 09:17:18 +01:00
Angel
40caf3f51a Adding support for Spanish language
Signed-off-by: Angel <ahsec>
2019-11-12 22:58:02 -08:00
Simone Margaritelli
21f1273bd8 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-11-13 01:32:14 +01:00
Simone Margaritelli
a8c07ba997 fix: supporting channels greater than 140 for 5g (closes #583) 2019-11-13 01:32:05 +01:00
evilsocket
6bd09c7f43 Merge pull request #595 from qbit/macos_and_posix_fix
Remove '-w' as macOS doesn't have it. Fix var expansion in dash.
2019-11-13 01:17:58 +01:00
Aaron Bieber
1830a19b37 Remove '-w' as macOS doesn't have it. Fix var expansion in dash.
Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2019-11-12 16:44:46 -07:00
Simone Margaritelli
9dcc647656 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-11-13 00:11:59 +01:00
Simone Margaritelli
8fcfd4cafd fix: plugin events dispatch is now asynchronous (fixes #592) 2019-11-13 00:11:50 +01:00
evilsocket
369d7a65a8 Merge pull request #578 from michelep/master
Add support for SpotPear 2,4inch LCD display via framebuffer
2019-11-12 23:58:30 +01:00
evilsocket
d7ad8ee0d7 Merge pull request #594 from dipsylala/additional_agent_properties
Providing APs/APs per channel and current channel as R/O from Agent
2019-11-12 23:57:46 +01:00
Simone Margaritelli
a5f9b9b2ee new: encountered units 2019-11-12 23:56:59 +01:00
Dispsylala
b266671864 Providing APs/APs per channel and current channel as R/O from Agent 2019-11-12 22:51:10 +00:00
Simone Margaritelli
c47b8f2d11 misc: small fix or general refactoring i did not bother commenting 2019-11-12 23:25:56 +01:00
Simone Margaritelli
8129fb7dd2 misc: small fix or general refactoring i did not bother commenting 2019-11-12 23:13:32 +01:00
Simone Margaritelli
c16cabc852 new: searchbox in the pwnmail 2019-11-12 23:08:39 +01:00
Simone Margaritelli
9a7de86057 new: fingerprint qrcode 2019-11-12 23:04:54 +01:00
Simone Margaritelli
440f2a470a new: added basic authentication to the web ui 2019-11-12 21:49:23 +01:00
Simone Margaritelli
81a89d43e0 misc: refactored ui.display.video as ui.web 2019-11-12 21:19:31 +01:00
Simone Margaritelli
91b409053b fix: using static assets 2019-11-12 21:03:21 +01:00
Simone Margaritelli
df01a03a4b new: pwngrid web client 2019-11-12 20:18:02 +01:00
evilsocket
e2be21004d Merge pull request #589 from luclu7/patch-1
Fixed a small typo in bt-tether
2019-11-12 13:18:42 +01:00
Luclu7
0f3d9db01d Fixed a small typo in bt-tether 2019-11-12 11:46:04 +01:00
evilsocket
19abc17816 Merge pull request #581 from dwi/patch-1
Small UPS Lite typo fix
2019-11-12 11:39:59 +01:00
evilsocket
4b9ebc2512 Merge pull request #586 from benleb/patch-2
shift word
2019-11-12 11:39:16 +01:00
evilsocket
41a3fad43e Merge pull request #587 from benleb/patch-3
fix baudrate option name in gps plugin
2019-11-12 11:39:00 +01:00
Ben Lebherz
f4b886cf7b fix baudrate option name 2019-11-12 08:37:36 +01:00
Ben Lebherz
0eb8e1829e shift word 2019-11-12 07:40:48 +01:00
hmax42
30b1874a0a New defaults for buttonshim 2019-11-11 19:08:12 +01:00
hmax42
b903f636d2 Blinking works now freely 2019-11-11 19:04:56 +01:00
dwi
52b40f049e Small UPS Lite typo fix
Fixing dfd534ac41 that fixes #521
2019-11-11 17:43:20 +01:00
evilsocket
3df35ef03b Merge pull request #580 from qbit/bkp_rstr_compat
Make backup / restore use POSIX shell for portability.
2019-11-11 17:37:11 +01:00
evilsocket
e87bcc4744 Merge pull request #577 from benleb/patch-1
fix completely broken gps plugin
2019-11-11 17:36:12 +01:00
Simone Margaritelli
dfd534ac41 fix: fixed ups_lite plugin layout (closes #521) 2019-11-11 17:35:45 +01:00
Aaron Bieber
0b2c156d29 Make backup / restore use POSIX shell for portability.
backup.sh:
  - Add use getopts for a more friendly user interface.
  - Add timeout to ping check.
  - Add unix epoch to backup file names.
  - Backup ssh information (/etc/ssh, $USER/.ssh).

restore.sh:
  - Add use getopts for a more friendly user interface.
  - Add timeout to ping check.
  - If user doesn't specify a backup file to restore, attempt to
    find the latest for the host name passed in.

Signed-off-by: Aaron Bieber <aaron@bolddaemon.com>
2019-11-11 07:49:24 -07:00
O-Zone
14064c3b5b Add support for SpotPear 2,4inch LCD display via framebuffer 2019-11-11 14:15:48 +01:00
Ben Lebherz
313fd66634 fix completely broken gps plugin :D 2019-11-11 14:12:38 +01:00
evilsocket
c939af4248 Merge pull request #575 from xenDE/master
added webgpsmap plugin
2019-11-11 11:00:57 +01:00
xenDE
e934181606 webgpsmap default disabled
was enabled default by mistake (copy&paste)
2019-11-11 10:59:54 +01:00
evilsocket
2505cbf14c Merge pull request #562 from DaniloNC/master
Add support to whitelist by MAC and MAC vendor
2019-11-11 10:51:57 +01:00
evilsocket
b2c6de72cd Merge pull request #574 from budd3993/fix-memtemp-inky
fixed memtemp location for inky display
2019-11-11 10:51:22 +01:00
evilsocket
307b3890f1 Merge pull request #567 from LaurentFough/master
Create .editorconfig
2019-11-11 10:50:26 +01:00
xenDE
b9a909de2b add handling for empty position data 2019-11-10 21:56:36 +00:00
Łaurent ʘ❢Ŧ Ŧough
b180f16aa6 Update .editorconfig
Removed; defaults that may not be used by everyone
2019-11-10 12:56:52 -08:00
Łaurent ʘ❢Ŧ Ŧough
2d517e3de5 Update .editorconfig
Removed *.py section
2019-11-10 12:55:28 -08:00
xenDE
a1746da7f1 comment default gpio button command examples 2019-11-10 19:45:43 +00:00
xenDE
1a1a70d6e8 added webgpsmap plugin 2019-11-10 19:34:49 +00:00
Josh Bauer
229e2671e8 fixed memtemp location for inky display
Signed-off-by: Josh Bauer <joshbauer3@gmail.com>
2019-11-10 14:08:33 -05:00
hmax42
92c1b6b005 blinking with static colors 2019-11-10 13:44:35 +01:00
Simone Margaritelli
a2ac679499 new: pwnfile link in the web ui (closes #557) 2019-11-10 13:24:56 +01:00
evilsocket
d7e1c59709 Merge pull request #569 from danielhoherd/improve-backup
Ignore backup archives. Use 'find | xargs' to handle missing files.
2019-11-10 13:11:30 +01:00
evilsocket
a5ed09cd08 Merge pull request #566 from xenDE/master
prevent double execution on gpio buttons
2019-11-10 13:10:49 +01:00
Simone Margaritelli
8c83f8129c fix: added memtemp defaults (fixes for PR #564) 2019-11-10 13:10:16 +01:00
hmax42
eddfdb3ebc Plugin for the Pimoroni Button Shim
this plugin enabled support for the 5 buttons of the shim. the rgb led is not supported yet. an example for config.yml lloks like this

      buttonshim:
        enabled: true
        buttons:
          - "date >> /home/pi/buttonA.txt"
          - "date >> /home/pi/buttonB.txt"
          - "date >> /home/pi/buttonC.txt"
          - "date >> /home/pi/buttonD.txt"
          - "date >> /home/pi/buttonE.txt"
2019-11-10 08:54:04 +01:00
Daniel Hoherd
7ca1168fed Ignore backup archives. Use 'find | xargs' to handle missing files. Correct file type in comments.
Signed-off-by: Daniel Hoherd <daniel.hoherd@gmail.com>
2019-11-09 17:20:42 -08:00
Łaurent ʘ❢Ŧ Ŧough
d41e5c1152 Create .editorconfig
Reasonably tame .editorconfig for help with editting the .py & especially the YAML files
2019-11-09 14:18:03 -08:00
xenDE
25eee18e7b verhindern von doppelten ausführungen
i had with the old value 250 many double executions, despite short push switches. The new value 600 prevents this. Tested with volume buttons.
2019-11-09 19:50:47 +01:00
evilsocket
38144a7abb Merge pull request #561 from mil1200/master
Added Slovak language
2019-11-09 10:30:49 +01:00
evilsocket
1f17d3cbbe Merge pull request #564 from bensmith83/patch-1
Add Fahrenheit and Kelvin temperature scales to memtemp.py
2019-11-09 10:30:26 +01:00
bensmith83
1da59b50b4 Add Fahrenheit and Kelvin temperature scales to memtemp.py 2019-11-08 19:50:33 -05:00
danilonc
1130c72098 Add support to whitelist by MAC and MAC vendor 2019-11-08 16:18:42 -06:00
Milan Kyselica
6b99deb7bd Update voice.po 2019-11-08 20:13:05 +01:00
Milan Kyselica
c3ed3509e9 + SK voice.mo 2019-11-08 20:12:23 +01:00
Milan Kyselica
e3a2e8c811 Delete voice.po 2019-11-08 20:12:04 +01:00
Milan Kyselica
89046bf0c5 + SK voice.po 2019-11-08 20:11:51 +01:00
Milan Kyselica
4cc61322de + SK voice.po 2019-11-08 20:09:53 +01:00
evilsocket
94521f2174 Merge pull request #559 from dadav/feature/webcfg-plugin
Add webcfg
2019-11-08 18:43:48 +01:00
dadav
b50acd364c Add webcfg 2019-11-08 17:55:48 +01:00
Simone Margaritelli
9bc7fcccb3 new: the grid plugin now triggers an on_unread_inbox event that other plugins can intercept (see led plugin) 2019-11-08 15:49:49 +01:00
Simone Margaritelli
bd61196c3c new: the auto-update plugin now triggers an on_updating event that other plugins can intercept (see led plugin) 2019-11-08 15:47:12 +01:00
Simone Margaritelli
186042aa20 misc: small fix or general refactoring i did not bother commenting 2019-11-08 15:43:57 +01:00
Simone Margaritelli
78bf801273 misc: small fix or general refactoring i did not bother commenting 2019-11-08 15:40:54 +01:00
Simone Margaritelli
89450ec1bd new: implemented led plugin (closes #522) 2019-11-08 15:31:40 +01:00
Simone Margaritelli
09f80cc842 fix: fixed typo in the example plugin 2019-11-08 14:08:43 +01:00
Simone Margaritelli
fcb5c87ef0 fix: enabling fstrim.timer from setup.py for updating users 2019-11-08 13:36:27 +01:00
Simone Margaritelli
bf0e480266 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-11-08 13:28:53 +01:00
Simone Margaritelli
fce57ad8eb misc: small fix or general refactoring i did not bother commenting 2019-11-08 13:28:37 +01:00
evilsocket
425fe7e55a Merge pull request #551 from Spindel/devel
Enable fstrim.timer to increase SDCard lifetime
2019-11-08 13:26:49 +01:00
Simone Margaritelli
5760864495 misc: small fix or general refactoring i did not bother commenting 2019-11-08 13:25:55 +01:00
Simone Margaritelli
3d9c559cdb misc: small fix or general refactoring i did not bother commenting 2019-11-08 13:20:14 +01:00
Simone Margaritelli
97a019fe25 new: implemented log rotation (closes #527) 2019-11-08 13:01:50 +01:00
Simone Margaritelli
8d5834232b fix: fixed exit after --version 2019-11-08 12:07:14 +01:00
evilsocket
ebc161e82f Merge pull request #554 from uzerai/master
Add  norwegian locale.
2019-11-08 10:58:30 +01:00
Edvard Botten
9485e53484 Add norwegian translation files.
Signed-off-by: Edvard Botten <edvbot@gmail.com>
2019-11-07 22:02:28 +00:00
D.S. Ljungmark
0d66f93ef3 Enable fstrim.timer to increase SDCard lifetime
fstrim timer triggers weekly to issue TRIM/DISCARD on devices that support it,
which includes SD and MMC devices.

This marks all unused diskspace as available for internal garbage collection
and wear levelling on the card.  It's preferrable to use the timer to the
discard option, as it involves a one-off cost of latency over a global slowing
down of all write or erase requests that the `discard` mount option does.
2019-11-07 17:41:17 +01:00
evilsocket
ad80fab554 Merge pull request #545 from dadav/fix/webhook_arguments
Changed webhook arguments, added exception handling, added logrotation
2019-11-07 11:06:14 +01:00
dadav
b7380018f1 Changed webhook arguments and add exception handling 2019-11-07 11:03:37 +01:00
Simone Margaritelli
2ea8e7fe6b misc: moved non-core and problematic plugins to separate repository (closes #542) 2019-11-07 10:59:40 +01:00
evilsocket
e23e1affae Merge pull request #538 from neutralinsomniac/normalize_waveshare29inch_config
normalize the waveshare29inch config string
2019-11-07 10:39:43 +01:00
evilsocket
8d8333b586 Merge pull request #546 from dadav/fix/gpio-plugin
Fix options of gpio-plugin
2019-11-07 10:38:16 +01:00
dadav
5712f5cd51 Fix arguments 2019-11-07 07:37:41 +01:00
Jeremy O'Brien
9cc15403c3 normalize the waveshare29inch config string
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-11-06 09:07:27 -05:00
evilsocket
15fa7039e8 Merge pull request #535 from deveth0/deveth0-523-webui
#523: Add some styling to webui
2019-11-06 11:31:40 +01:00
Alex Muthmann
f83c820b38 Fix missing js 2019-11-06 11:28:23 +01:00
evilsocket
323c9a74cc Merge pull request #531 from deveth0/deveth0-netpos-logging
Additional Logging for net-pos plugin
2019-11-06 11:24:14 +01:00
evilsocket
8a2d6eac9d Merge pull request #537 from FrixosTh/patch-3
Bug Fix on AircrackOnly Plugin preventing it to load
2019-11-06 11:23:36 +01:00
FrixosTh
61d8e28aad Bug Fix on AircrackOnly Plugin preventing it to load
Unnecessary/Wrong call to the super init method prevents AircrackOnly to load during startup
2019-11-06 01:46:48 +02:00
Alex Muthmann
337ebd6f9f Add some style 2019-11-06 00:06:19 +01:00
amuthmann
399dbf2b41 Fixes #523: Add some style 2019-11-06 00:03:29 +01:00
amuthmann
f952bcd298 Fixes #523: Add some style 2019-11-06 00:03:17 +01:00
Alex Muthmann
9dc7c92c86 Additional Logging for net-pos plugin to simplify search for broken files
I currently have some broken net-pos files on my device and the logging does not provide enough information to find the invalid files. I'd suggest to log the path.
2019-11-05 21:54:37 +01:00
411 changed files with 77023 additions and 1545 deletions

31
.editorconfig Normal file
View File

@@ -0,0 +1,31 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[Makefile]
indent_style = tab
[*.py]
indent_style = space
indent_size = 4
[*.json]
insert_final_newline = ignore
[*.js]
indent_style = ignore
insert_final_newline = ignore
[*.{md,txt}]
indent_size = 4
trim_trailing_whitespace = false

3
.gitignore vendored
View File

@@ -15,3 +15,6 @@ output-pwnagotchi
build
dist
pwnagotchi.egg-info
*backup*.tgz
*backup*.gz
.vscode

View File

@@ -1,10 +1,11 @@
PACKER_VERSION=1.5.2
PWN_HOSTNAME=pwnagotchi
PWN_VERSION=master
all: clean install image
install:
curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
curl https://releases.hashicorp.com/packer/$(PACKER_VERSION)/packer_$(PACKER_VERSION)_linux_amd64.zip -o /tmp/packer.zip
unzip /tmp/packer.zip -d /tmp
sudo mv /tmp/packer /usr/bin/packer
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image

View File

@@ -1,24 +1,98 @@
#!/usr/bin/python3
import logging
import argparse
import time
import signal
import sys
import toml
import pwnagotchi
from pwnagotchi import utils
from pwnagotchi.plugins import cmd as plugins_cmd
from pwnagotchi import log
from pwnagotchi import restart
from pwnagotchi import fs
from pwnagotchi.utils import DottedTomlEncoder
def do_clear(display):
logging.info("clearing the display ...")
display.clear()
sys.exit(0)
def do_manual_mode(agent):
logging.info("entering manual mode ...")
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session:
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.duration_human,
agent.last_session.epochs,
agent.last_session.train_epochs,
agent.last_session.avg_reward,
agent.last_session.min_reward,
agent.last_session.max_reward))
while True:
display.on_manual_mode(agent.last_session)
time.sleep(5)
if grid.is_connected():
plugins.on('internet_available', agent)
def do_auto_mode(agent):
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.start()
while True:
try:
# recon on all channels
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# for each channel
for ch, aps in channels:
agent.set_channel(ch)
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# WiFi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:
logging.exception("main loop exception (%s)", e)
if __name__ == '__main__':
import argparse
import time
import logging
import yaml
import pwnagotchi
import pwnagotchi.grid as grid
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.identity import KeyPair
from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display
parser = argparse.ArgumentParser()
parser = plugins_cmd.add_parsers(parser)
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.yml',
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.toml',
help='Main configuration file.')
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml',
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.toml',
help='If this file exists, configuration will be merged and this will override default values.')
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
@@ -32,95 +106,61 @@ if __name__ == '__main__':
help="Enable debug logs.")
parser.add_argument('--version', dest="version", action="store_true", default=False,
help="Prints the version.")
help="Print the version.")
parser.add_argument('--print-config', dest="print_config", action="store_true", default=False,
help="Print the configuration.")
args = parser.parse_args()
if plugins_cmd.used_plugin_cmd(args):
config = utils.load_config(args)
log.setup_logging(args, config)
rc = plugins_cmd.handle_cmd(args, config)
sys.exit(rc)
if args.version:
print(pwnagotchi.version)
SystemExit(0)
print(pwnagotchi.__version__)
sys.exit(0)
config = utils.load_config(args)
utils.setup_logging(args, config)
if args.print_config:
print(toml.dumps(config, encoder=DottedTomlEncoder()))
sys.exit(0)
from pwnagotchi.identity import KeyPair
from pwnagotchi.agent import Agent
from pwnagotchi.ui import fonts
from pwnagotchi.ui.display import Display
from pwnagotchi import grid
from pwnagotchi import plugins
pwnagotchi.config = config
fs.setup_mounts(config)
log.setup_logging(args, config)
fonts.init(config)
pwnagotchi.set_name(config['main']['name'])
plugins.load(config)
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
keypair = KeyPair(view=display)
agent = Agent(view=display, config=config, keypair=keypair)
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version))
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" % (plugin.__class__.__name__, plugin.__version__))
if args.do_clear:
logging.info("clearing the display ...")
display.clear()
do_clear(display)
sys.exit(0)
elif args.do_manual:
logging.info("entering manual mode ...")
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session:
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
agent.last_session.duration_human,
agent.last_session.epochs,
agent.last_session.train_epochs,
agent.last_session.avg_reward,
agent.last_session.min_reward,
agent.last_session.max_reward))
def usr1_handler(*unused):
logging.info('Received USR1 singal. Restart process ...')
restart("MANU" if args.do_manual else "AUTO")
while True:
display.on_manual_mode(agent.last_session)
time.sleep(5)
if grid.is_connected():
plugins.on('internet_available', agent)
signal.signal(signal.SIGUSR1, usr1_handler)
if args.do_manual:
do_manual_mode(agent)
else:
logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.start()
while True:
try:
# recon on all channels
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# for each channel
for ch, aps in channels:
agent.set_channel(ch)
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# WiFi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:
logging.exception("main loop exception")
do_auto_mode(agent)

View File

@@ -6,10 +6,15 @@ After=pwngrid-peer.service
[Service]
Type=simple
WorkingDirectory=/tmp
PermissionsStartOnly=true
ExecStart=/usr/bin/pwnagotchi-launcher
Restart=always
RestartSec=30
TasksMax=infinity
LimitNPROC=infinity
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target

View File

@@ -1,6 +1,14 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
# start mon0
start_monitor_interface

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
HTML_FORM = """
<!DOCTYPE html>
<html>
<head>
<title>Decryption</title>
<style>
body { text-align: center; padding: 150px; }
h1 { font-size: 50px; }
body { font: 20px Helvetica, sans-serif; color: #333; }
article { display: block; text-align: center; width: 650px; margin: 0 auto;}
input {
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: 1px solid #ccc;
}
input[type=password] {
width: 75%;
font-size: 24px;
}
input[type=submit] {
cursor: pointer;
width: 75%;
}
input[type=submit]:hover {
background-color: #d9d9d9;
}
</style>
</head>
<body>
<article>
<h1>Decryption</h1>
<p>Some of your files are encrypted.</p>
<p>Please provide the decryption password.</p>
<div>
<form action="/set-password" method="POST">
<input type="password" id="password" name="password" value=""><br>
<input type="submit" value="Submit">
</form>
</div>
</article>
</body>
</html>
"""
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(HTML_FORM.encode())
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
self.send_response(200)
self.end_headers()
password = body.decode('UTF-8').split('=')[1]
with open('/tmp/.pwnagotchi-secret', 'wt') as pwfile:
pwfile.write(password)
httpd = HTTPServer(('0.0.0.0', 80), SimpleHTTPRequestHandler)
httpd.serve_forever()

View File

@@ -1,6 +1,14 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# we need to decrypt something
if is_crypted_mode; then
while ! is_decrypted; do
echo "Waiting for decryption..."
sleep 1
done
fi
# blink 10 times to signal ready state
blink_led 10 &
@@ -8,4 +16,4 @@ if is_auto_mode; then
/usr/local/bin/pwnagotchi
else
/usr/local/bin/pwnagotchi --manual
fi
fi

View File

@@ -84,4 +84,80 @@ is_auto_mode_no_delete() {
# no override, but none of the interfaces is up -> AUTO
return 0
}
}
# check if we need to decrypt something
is_crypted_mode() {
if [ -f /root/.pwnagotchi-crypted ]; then
return 0
fi
return 1
}
# decryption loop
is_decrypted() {
while read -r mapping container mount; do
# mapping = name the device or file will be mapped to
# container = the luks encrypted device or file
# mount = the mountpoint
# fail if not mounted
if ! mountpoint -q "$mount" >/dev/null 2>&1; then
if [ -f /tmp/.pwnagotchi-secret ]; then
</tmp/.pwnagotchi-secret read -r SECRET
if ! test -b /dev/disk/by-id/dm-uuid-*"$(cryptsetup luksUUID "$container" | tr -d -)"*; then
if echo -n "$SECRET" | cryptsetup luksOpen -d- "$container" "$mapping" >/dev/null 2>&1; then
echo "Container decrypted!"
fi
fi
if mount /dev/mapper/"$mapping" "$mount" >/dev/null 2>&1; then
echo "Mounted /dev/mapper/$mapping to $mount"
continue
fi
fi
if ! ip -4 addr show wlan0 | grep inet >/dev/null 2>&1; then
>/dev/null 2>&1 ip addr add 192.168.0.10/24 dev wlan0
fi
if ! pgrep -f decryption-webserver >/dev/null 2>&1; then
>/dev/null 2>&1 decryption-webserver &
fi
if ! pgrep wpa_supplicant >/dev/null 2>&1; then
>/tmp/wpa_supplicant.conf cat <<EOF
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
ap_scan=2
network={
ssid="DECRYPT-ME"
mode=2
key_mgmt=WPA-PSK
psk="pwnagotchi"
frequency=2437
}
EOF
>/dev/null 2>&1 wpa_supplicant -D nl80211 -i wlan0 -c /tmp/wpa_supplicant.conf &
fi
if ! pgrep dnsmasq >/dev/null 2>&1; then
>/dev/null 2>&1 dnsmasq -k -p 53 -h -O "6,192.168.0.10" -A "/#/192.168.0.10" -i wlan0 -K -F 192.168.0.50,192.168.0.60,255.255.255.0,24h &
fi
return 1
fi
done </root/.pwnagotchi-crypted
# overwrite password
>/tmp/.pwnagotchi-secret python3 -c 'print("A"*4096)'
sync # flush
pkill wpa_supplicant
pkill dnsmasq
kill "$(pgrep -f "decryption-webserver")"
return 0
}

View File

@@ -98,6 +98,7 @@
{
"type": "ansible-local",
"playbook_file": "pwnagotchi.yml",
"extra_arguments": [ "--extra-vars \"ansible_python_interpreter=/usr/bin/python3\"" ],
"command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook"
},
{

View File

@@ -23,6 +23,7 @@
- bettercap.service
- pwngrid-peer.service
- epd-fuse.service
- fstrim.timer
disable:
- apt-daily.timer
- apt-daily.service
@@ -34,7 +35,7 @@
- ifup@wlan0.service
packages:
bettercap:
url: "https://github.com/bettercap/bettercap/releases/download/v2.26.1/bettercap_linux_armhf_v2.26.1.zip"
url: "https://github.com/bettercap/bettercap/releases/download/v2.27.1/bettercap_linux_armhf_v2.27.1.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid:
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.1/pwngrid_linux_armhf_v1.10.1.zip"
@@ -46,12 +47,13 @@
- firmware-misc-nonfree
- firmware-realtek
remove:
- rasberrypi-net-mods
- raspberrypi-net-mods
- dhcpcd5
- triggerhappy
- wpa_supplicant
- nfs-common
install:
- rsync
- vim
- screen
- golang
@@ -99,9 +101,9 @@
- bc
- fonts-freefont-ttf
- fbi
- python3-flask
- python3-flask-cors
- python3-flaskext.wtf
- fonts-ipaexfont-gothic
- cryptsetup
- dnsmasq
tasks:
- name: change hostname
@@ -215,9 +217,19 @@
dest: /usr/local/src/pwnagotchi
register: pwnagotchigit
- name: create /usr/local/share/pwnagotchi/ folder
file:
path: /usr/local/share/pwnagotchi/
state: directory
- name: clone pwnagotchi plugins repository
git:
repo: https://github.com/evilsocket/pwnagotchi-plugins-contrib.git
dest: /usr/local/share/pwnagotchi/availaible-plugins
- name: fetch pwnagotchi version
set_fact:
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/__init__.py') | replace('\n', ' ') | regex_replace('.*version.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
pwnagotchi_version: "{{ lookup('file', '/usr/local/src/pwnagotchi/pwnagotchi/_version.py') | regex_replace('.*__version__.*=.*''([0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]*)''.*', '\\1') }}"
- name: pwnagotchi version found
debug:
@@ -227,7 +239,7 @@
command: "python3 setup.py sdist bdist_wheel"
args:
chdir: /usr/local/src/pwnagotchi
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: install opencv-python
pip:
@@ -245,7 +257,7 @@
pip:
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
extra_args: "--no-cache-dir"
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi.version)
when: (pwnagotchigit.changed) or (pip_packages['pwnagotchi'] is undefined) or (pip_packages['pwnagotchi'] != pwnagotchi_version)
- name: download and install pwngrid
unarchive:

View File

@@ -1,14 +1,14 @@
import subprocess
import os
import logging
import time
import re
import pwnagotchi.ui.view as view
import pwnagotchi
version = '1.2.1'
from pwnagotchi._version import __version__
_name = None
config = None
def set_name(new_name):
@@ -27,17 +27,17 @@ def set_name(new_name):
if new_name != current:
global _name
logging.info("setting unit hostname '%s' -> '%s'" % (current, new_name))
logging.info("setting unit hostname '%s' -> '%s'", current, new_name)
with open('/etc/hostname', 'wt') as fp:
fp.write(new_name)
with open('/etc/hosts', 'rt') as fp:
prev = fp.read()
logging.debug("old hosts:\n%s\n" % prev)
logging.debug("old hosts:\n%s\n", prev)
with open('/etc/hosts', 'wt') as fp:
patched = prev.replace(current, new_name, -1)
logging.debug("new hosts:\n%s\n" % patched)
logging.debug("new hosts:\n%s\n", patched)
fp.write(patched)
os.system("hostname '%s'" % new_name)
@@ -65,8 +65,6 @@ def mem_usage():
kb_mem_total = int(line.split()[1])
if line.startswith("MemFree:"):
kb_mem_free = int(line.split()[1])
if line.startswith("MemAvailable:"):
kb_mem_available = int(line.split()[1])
if line.startswith("Buffers:"):
kb_main_buffers = int(line.split()[1])
if line.startswith("Cached:"):
@@ -77,18 +75,27 @@ def mem_usage():
return 0
def cpu_load():
def _cpu_stat():
"""
Returns the splitted first line of the /proc/stat file
"""
with open('/proc/stat', 'rt') as fp:
for line in fp:
line = line.strip()
if line.startswith('cpu '):
parts = list(map(int, line.split()[1:]))
user_n = parts[0]
sys_n = parts[2]
idle_n = parts[3]
tot = user_n + sys_n + idle_n
return (user_n + sys_n) / tot
return 0
return list(map(int,fp.readline().split()[1:]))
def cpu_load():
"""
Returns the current cpuload
"""
parts0 = _cpu_stat()
time.sleep(0.1)
parts1 = _cpu_stat()
parts_diff = [p1 - p0 for (p0, p1) in zip(parts0, parts1)]
user, nice, sys, idle, iowait, irq, softirq, steal, _guest, _guest_nice = parts_diff
idle_sum = idle + iowait
non_idle_sum = user + nice + sys + irq + softirq + steal
total = idle_sum + non_idle_sum
return non_idle_sum / total
def temperature(celsius=True):
@@ -99,7 +106,15 @@ def temperature(celsius=True):
def shutdown():
logging.warning("syncing...")
from pwnagotchi import fs
for m in fs.mounts:
m.sync()
logging.warning("shutting down ...")
from pwnagotchi.ui import view
if view.ROOT:
view.ROOT.on_shutdown()
# give it some time to refresh the ui
@@ -109,7 +124,7 @@ def shutdown():
def restart(mode):
logging.warning("restarting in %s mode ..." % mode)
logging.warning("restarting in %s mode ...", mode)
if mode == 'AUTO':
os.system("touch /root/.pwnagotchi-auto")
@@ -123,10 +138,11 @@ def restart(mode):
def reboot(mode=None):
if mode is not None:
mode = mode.upper()
logging.warning("rebooting in %s mode ..." % mode)
logging.warning("rebooting in %s mode ...", mode)
else:
logging.warning("rebooting ...")
from pwnagotchi.ui import view
if view.ROOT:
view.ROOT.on_rebooting()
# give it some time to refresh the ui

1
pwnagotchi/_version.py Normal file
View File

@@ -0,0 +1 @@
__version__ = '1.5.1'

View File

@@ -3,6 +3,7 @@ import json
import os
import re
import logging
import asyncio
import _thread
import pwnagotchi
@@ -30,12 +31,14 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
AsyncTrainer.__init__(self, config)
self._started_at = time.time()
self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter'])
self._filter = None if not config['main']['filter'] else re.compile(config['main']['filter'])
self._current_channel = 0
self._tot_aps = 0
self._aps_on_channel = 0
self._supported_channels = utils.iface_channels(config['main']['iface'])
self._view = view
self._view.set_agent(self)
self._web_ui = Server(self, self._config['ui']['display'])
self._web_ui = Server(self, config['ui'])
self._access_points = []
self._last_pwnd = None
@@ -47,6 +50,10 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
if not os.path.exists(config['bettercap']['handshakes']):
os.makedirs(config['bettercap']['handshakes'])
logging.info("%s@%s (v%s)", pwnagotchi.name(), self.fingerprint(), pwnagotchi.__version__)
for _, plugin in plugins.loaded.items():
logging.debug("plugin '%s' v%s", plugin.__class__.__name__, plugin.__version__)
def config(self):
return self._config
@@ -57,12 +64,12 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
return self._supported_channels
def setup_events(self):
logging.info("connecting to %s ..." % self.url)
logging.info("connecting to %s ...", self.url)
for tag in self._config['bettercap']['silence']:
try:
self.run('events.ignore %s' % tag, verbose_errors=False)
except Exception as e:
except Exception:
pass
def _reset_wifi_settings(self):
@@ -84,7 +91,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
s = self.session()
for iface in s['interfaces']:
if iface['name'] == mon_iface:
logging.info("found monitor interface: %s" % iface['name'])
logging.info("found monitor interface: %s", iface['name'])
has_mon = True
break
@@ -93,11 +100,11 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
logging.info("starting monitor interface ...")
self.run('!%s' % mon_start_cmd)
else:
logging.info("waiting for monitor interface %s ..." % mon_iface)
logging.info("waiting for monitor interface %s ...", mon_iface)
time.sleep(1)
logging.info("supported channels: %s" % self._supported_channels)
logging.info("handshakes will be collected inside %s" % self._config['bettercap']['handshakes'])
logging.info("supported channels: %s", self._supported_channels)
logging.info("handshakes will be collected inside %s", self._config['bettercap']['handshakes'])
self._reset_wifi_settings()
@@ -115,9 +122,9 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def _wait_bettercap(self):
while True:
try:
s = self.session()
_s = self.session()
return
except:
except Exception:
logging.info("waiting for bettercap API to be available ...")
time.sleep(1)
@@ -128,6 +135,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
self.set_starting()
self.start_monitor_mode()
self.start_event_polling()
self.start_session_fetcher()
# print initial stats
self.next_epoch()
self.set_ready()
@@ -145,14 +153,14 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
if not channels:
self._current_channel = 0
logging.debug("RECON %ds" % recon_time)
logging.debug("RECON %ds", recon_time)
self.run('wifi.recon.channel clear')
else:
logging.debug("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels))))
logging.debug("RECON %ds ON CHANNELS %s", recon_time, ','.join(map(str, channels)))
try:
self.run('wifi.recon.channel %s' % ','.join(map(str, channels)))
except Exception as e:
logging.exception("error")
logging.exception("Error while setting wifi.recon.channels (%s)", e)
self.wait_for(recon_time, sleeping=False)
@@ -176,15 +184,26 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
for ap in s['wifi']['aps']:
if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
continue
elif ap['hostname'] not in whitelist:
elif ap['hostname'] not in whitelist \
and ap['mac'].lower() not in whitelist \
and ap['mac'][:8].lower() not in whitelist:
if self._filter_included(ap):
aps.append(ap)
except Exception as e:
logging.exception("error")
logging.exception("Error while getting acces points (%s)", e)
aps.sort(key=lambda ap: ap['channel'])
return self.set_access_points(aps)
def get_total_aps(self):
return self._tot_aps
def get_aps_on_channel(self):
return self._aps_on_channel
def get_current_channel(self):
return self._current_channel
def get_access_points_by_channel(self):
aps = self.get_access_points()
channels = self._config['personality']['channels']
@@ -195,7 +214,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
ch = ap['channel']
# if we're sticking to a channel, skip anything
# which is not on that channel
if channels != [] and ch not in channels:
if channels and ch not in channels:
continue
if ch not in grouped:
@@ -221,16 +240,16 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
# self._view.set('epoch', '%04d' % self._epoch.epoch)
def _update_counters(self):
tot_aps = len(self._access_points)
self._tot_aps = len(self._access_points)
tot_stas = sum(len(ap['clients']) for ap in self._access_points)
if self._current_channel == 0:
self._view.set('aps', '%d' % tot_aps)
self._view.set('aps', '%d' % self._tot_aps)
self._view.set('sta', '%d' % tot_stas)
else:
aps_on_channel = len([ap for ap in self._access_points if ap['channel'] == self._current_channel])
self._aps_on_channel = len([ap for ap in self._access_points if ap['channel'] == self._current_channel])
stas_on_channel = sum(
[len(ap['clients']) for ap in self._access_points if ap['channel'] == self._current_channel])
self._view.set('aps', '%d (%d)' % (aps_on_channel, tot_aps))
self._view.set('aps', '%d (%d)' % (self._aps_on_channel, self._tot_aps))
self._view.set('sta', '%d (%d)' % (stas_on_channel, tot_stas))
def _update_handshakes(self, new_shakes=0):
@@ -257,7 +276,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
pwnagotchi.reboot()
def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
logging.warning("writing recovery data to %s ...", RECOVERY_DATA_FILE)
with open(RECOVERY_DATA_FILE, 'w') as fp:
data = {
'started_at': self._started_at,
@@ -272,7 +291,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
try:
with open(RECOVERY_DATA_FILE, 'rt') as fp:
data = json.load(fp)
logging.info("found recovery data: %s" % data)
logging.info("found recovery data: %s", data)
self._started_at = data['started_at']
self._epoch.epoch = data['epoch']
self._handshakes = data['handshakes']
@@ -280,64 +299,73 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
self._last_pwnd = data['last_pwnd']
if delete:
logging.info("deleting %s" % RECOVERY_DATA_FILE)
logging.info("deleting %s", RECOVERY_DATA_FILE)
os.unlink(RECOVERY_DATA_FILE)
except:
if not no_exceptions:
raise
def _event_poller(self):
self._load_recovery_data()
def start_session_fetcher(self):
_thread.start_new_thread(self._fetch_stats, ())
def _fetch_stats(self):
while True:
s = self.session()
self._update_uptime(s)
self._update_advertisement(s)
self._update_peers()
self._update_counters()
self._update_handshakes(0)
time.sleep(1)
async def _on_event(self, msg):
found_handshake = False
jmsg = json.loads(msg)
if jmsg['tag'] == 'wifi.client.handshake':
filename = jmsg['data']['file']
sta_mac = jmsg['data']['station']
ap_mac = jmsg['data']['ap']
key = "%s -> %s" % (sta_mac, ap_mac)
if key not in self._handshakes:
self._handshakes[key] = jmsg
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
if ap_and_station is None:
logging.warning("!!! captured new handshake: %s !!!", key)
self._last_pwnd = ap_mac
plugins.on('handshake', self, filename, ap_mac, sta_mac)
else:
(ap, sta) = ap_and_station
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
'hostname'] != '<hidden>' else ap_mac
logging.warning(
"!!! captured new handshake on channel %d, %d dBm: %s (%s) -> %s [%s (%s)] !!!",
ap['channel'],
ap['rssi'],
sta['mac'], sta['vendor'],
ap['hostname'], ap['mac'], ap['vendor'])
plugins.on('handshake', self, filename, ap, sta)
found_handshake = True
self._update_handshakes(1 if found_handshake else 0)
def _event_poller(self, loop):
self._load_recovery_data()
self.run('events.clear')
while True:
time.sleep(1)
new_shakes = 0
logging.debug("polling events ...")
try:
s = self.session()
self._update_uptime(s)
self._update_advertisement(s)
self._update_peers()
self._update_counters()
for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']:
filename = h['data']['file']
sta_mac = h['data']['station']
ap_mac = h['data']['ap']
key = "%s -> %s" % (sta_mac, ap_mac)
if key not in self._handshakes:
self._handshakes[key] = h
new_shakes += 1
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
if ap_and_station is None:
logging.warning("!!! captured new handshake: %s !!!" % key)
self._last_pwnd = ap_mac
plugins.on('handshake', self, filename, ap_mac, sta_mac)
else:
(ap, sta) = ap_and_station
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
'hostname'] != '<hidden>' else ap_mac
logging.warning("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \
ap['channel'],
sta['mac'], sta['vendor'],
ap['hostname'], ap['mac'], ap['vendor']))
plugins.on('handshake', self, filename, ap, sta)
except Exception as e:
logging.error("error: %s" % e)
finally:
self._update_handshakes(new_shakes)
loop.create_task(self.start_websocket(self._on_event))
loop.run_forever()
except Exception as ex:
logging.debug("Error while polling via websocket (%s)", ex)
def start_event_polling(self):
_thread.start_new_thread(self._event_poller, ())
# start a thread and pass in the mainloop
_thread.start_new_thread(self._event_poller, (asyncio.new_event_loop(),))
def is_module_running(self, module):
s = self.session()
@@ -373,15 +401,15 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def associate(self, ap, throttle=0):
if self.is_stale():
logging.debug("recon is stale, skipping assoc(%s)" % ap['mac'])
logging.debug("recon is stale, skipping assoc(%s)", ap['mac'])
return
if self._config['personality']['associate'] and self._should_interact(ap['mac']):
self._view.on_assoc(ap)
try:
logging.info("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \
ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients'])))
logging.info("sending association frame to %s (%s %s) on channel %d [%d clients], %d dBm...",
ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients']), ap['rssi'])
self.run('wifi.assoc %s' % ap['mac'])
self._epoch.track(assoc=True)
except Exception as e:
@@ -394,15 +422,15 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def deauth(self, ap, sta, throttle=0):
if self.is_stale():
logging.debug("recon is stale, skipping deauth(%s)" % sta['mac'])
logging.debug("recon is stale, skipping deauth(%s)", sta['mac'])
return
if self._config['personality']['deauth'] and self._should_interact(sta['mac']):
self._view.on_deauth(sta)
try:
logging.info("deauthing %s (%s) from %s (%s %s) on channel %d ..." % (
sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel']))
logging.info("deauthing %s (%s) from %s (%s %s) on channel %d, %d dBm ...",
sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], ap['rssi'])
self.run('wifi.deauth %s' % sta['mac'])
self._epoch.track(deauth=True)
except Exception as e:
@@ -415,7 +443,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
def set_channel(self, channel, verbose=True):
if self.is_stale():
logging.debug("recon is stale, skipping set_channel(%d)" % channel)
logging.debug("recon is stale, skipping set_channel(%d)", channel)
return
# if in the previous loop no client stations has been deauthenticated
@@ -431,12 +459,12 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
if channel != self._current_channel:
if self._current_channel != 0 and wait > 0:
if verbose:
logging.info("waiting for %ds on channel %d ..." % (wait, self._current_channel))
logging.info("waiting for %ds on channel %d ...", wait, self._current_channel)
else:
logging.debug("waiting for %ds on channel %d ..." % (wait, self._current_channel))
logging.debug("waiting for %ds on channel %d ...", wait, self._current_channel)
self.wait_for(wait)
if verbose and self._epoch.any_activity:
logging.info("CHANNEL %d" % channel)
logging.info("CHANNEL %d", channel)
try:
self.run('wifi.recon.channel %d' % channel)
self._current_channel = channel
@@ -446,4 +474,4 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
plugins.on('channel_hop', self, channel)
except Exception as e:
logging.error("error: %s" % e)
logging.error("Error while setting channel (%s)", e)

View File

@@ -1,12 +1,9 @@
import os
import time
import warnings
import logging
# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'}
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
warnings.simplefilter(action='ignore', category=FutureWarning)
def load(config, agent, epoch, from_disk=True):
@@ -59,7 +56,7 @@ def load(config, agent, epoch, from_disk=True):
return a2c
except Exception as e:
logging.exception("error while starting AI")
logging.exception("error while starting AI (%s)", e)
logging.warning("[ai] AI not loaded!")
return False

View File

@@ -19,6 +19,10 @@ class Epoch(object):
self.active_for = 0
# number of epochs with no visible access points
self.blind_for = 0
# number of epochs in sad state
self.sad_for = 0
# number of epochs in bored state
self.bored_for = 0
# did deauth in this epoch in the current channel?
self.did_deauth = False
# number of deauths in this epoch
@@ -99,13 +103,13 @@ class Epoch(object):
try:
aps_per_chan[ch_idx] += 1.0
sta_per_chan[ch_idx] += len(ap['clients'])
except IndexError as e:
except IndexError:
logging.error("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
for peer in peers:
try:
peers_per_chan[peer.last_channel - 1] += 1.0
except IndexError as e:
except IndexError:
logging.error(
"got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
@@ -157,6 +161,20 @@ class Epoch(object):
else:
self.active_for += 1
self.inactive_for = 0
self.sad_for = 0
self.bored_for = 0
if self.inactive_for >= self.config['personality']['sad_num_epochs']:
# sad > bored; cant be sad and bored
self.bored_for = 0
self.sad_for += 1
elif self.inactive_for >= self.config['personality']['bored_num_epochs']:
# sad_treshhold > inactive > bored_treshhold; cant be sad and bored
self.sad_for = 0
self.bored_for += 1
else:
self.sad_for = 0
self.bored_for = 0
now = time.time()
cpu = pwnagotchi.cpu_load()
@@ -172,6 +190,8 @@ class Epoch(object):
'blind_for_epochs': self.blind_for,
'inactive_for_epochs': self.inactive_for,
'active_for_epochs': self.active_for,
'sad_for_epochs': self.sad_for,
'bored_for_epochs': self.bored_for,
'missed_interactions': self.num_missed,
'num_hops': self.num_hops,
'num_peers': self.num_peers,
@@ -188,13 +208,15 @@ class Epoch(object):
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
self._epoch_data_ready.set()
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d sad=%d bored=%d inactive=%d active=%d peers=%d tot_bond=%.2f "
"avg_bond=%.2f hops=%d missed=%d deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% "
"temperature=%dC reward=%s" % (
self.epoch,
utils.secs_to_hhmmss(self.epoch_duration),
utils.secs_to_hhmmss(self.num_slept),
self.blind_for,
self.sad_for,
self.bored_for,
self.inactive_for,
self.active_for,
self.num_peers,

View File

@@ -4,31 +4,37 @@ import pwnagotchi.mesh.wifi as wifi
MAX_EPOCH_DURATION = 1024
histogram_size = wifi.NumChannels
shape = (1,
# aps per channel
histogram_size +
# clients per channel
histogram_size +
# peers per channel
histogram_size +
# duration
1 +
# inactive
1 +
# active
1 +
# missed
1 +
# hops
1 +
# deauths
1 +
# assocs
1 +
# handshakes
1)
def describe(extended=False):
if not extended:
histogram_size = wifi.NumChannels
else:
# see https://github.com/evilsocket/pwnagotchi/issues/583
histogram_size = wifi.NumChannelsExt
return histogram_size, (1,
# aps per channel
histogram_size +
# clients per channel
histogram_size +
# peers per channel
histogram_size +
# duration
1 +
# inactive
1 +
# active
1 +
# missed
1 +
# hops
1 +
# deauths
1 +
# assocs
1 +
# handshakes
1)
def featurize(state, step):

View File

@@ -34,10 +34,14 @@ class Environment(gym.Env):
self._epoch_num = 0
self._last_render = None
channels = agent.supported_channels()
# see https://github.com/evilsocket/pwnagotchi/issues/583
self._supported_channels = agent.supported_channels()
self._extended_spectrum = any(ch > 140 for ch in self._supported_channels)
self._histogram_size, self._observation_shape = featurizer.describe(self._extended_spectrum)
Environment.params += [
Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in
range(featurizer.histogram_size) if ch + 1 in channels
range(self._histogram_size) if ch + 1 in self._supported_channels
]
self.last = {
@@ -50,7 +54,7 @@ class Environment(gym.Env):
}
self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable])
self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32)
self.observation_space = spaces.Box(low=0, high=1, shape=self._observation_shape, dtype=np.float32)
self.reward_range = reward.range
@staticmethod
@@ -118,7 +122,7 @@ class Environment(gym.Env):
return self.last['state_v']
def _render_histogram(self, hist):
for ch in range(featurizer.histogram_size):
for ch in range(self._histogram_size):
if hist[ch]:
logging.info(" CH %d: %s" % (ch + 1, hist[ch]))

View File

@@ -18,4 +18,10 @@ class RewardFunction(object):
m = -.3 * (state['missed_interactions'] / tot_interactions)
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
return h + a + c + b + i + m
# include emotions if state >= 5 epochs
_sad = state['sad_for_epochs'] if state['sad_for_epochs'] >= 5 else 0
_bored = state['bored_for_epochs'] if state['bored_for_epochs'] >= 5 else 0
s = -.2 * (_sad / tot_epochs)
l = -.1 * (_bored / tot_epochs)
return h + a + c + b + i + m + s + l

View File

@@ -176,7 +176,7 @@ class AsyncTrainer(object):
self.set_training(True, epochs_per_episode)
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
except Exception as e:
logging.exception("[ai] error while training")
logging.exception("[ai] error while training (%s)", e)
finally:
self.set_training(False)
obs = self._model.env.reset()

View File

@@ -12,19 +12,18 @@ class Automata(object):
self._epoch = Epoch(config)
def _on_miss(self, who):
logging.info("it looks like %s is not in range anymore :/" % who)
logging.info("it looks like %s is not in range anymore :/", who)
self._epoch.track(miss=True)
self._view.on_miss(who)
def _on_error(self, who, e):
error = "%s" % e
# when we're trying to associate or deauth something that is not in range anymore
# (if we are moving), we get the following error from bettercap:
# error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list.
if 'is an unknown BSSID' in error:
if 'is an unknown BSSID' in str(e):
self._on_miss(who)
else:
logging.error("%s" % e)
logging.error(e)
def set_starting(self):
self._view.on_starting()
@@ -58,7 +57,7 @@ class Automata(object):
def set_bored(self):
factor = self._epoch.inactive_for / self._config['personality']['bored_num_epochs']
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
logging.warning("%d epochs with no activity -> bored", self._epoch.inactive_for)
self._view.on_bored()
plugins.on('bored', self)
else:
@@ -68,7 +67,7 @@ class Automata(object):
def set_sad(self):
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
logging.warning("%d epochs with no activity -> sad", self._epoch.inactive_for)
self._view.on_sad()
plugins.on('sad', self)
else:
@@ -77,7 +76,7 @@ class Automata(object):
def set_angry(self, factor):
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> angry" % self._epoch.inactive_for)
logging.warning("%d epochs with no activity -> angry", self._epoch.inactive_for)
self._view.on_angry()
plugins.on('angry', self)
else:
@@ -85,7 +84,7 @@ class Automata(object):
self.set_grateful()
def set_excited(self):
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
logging.warning("%d epochs with activity -> excited", self._epoch.active_for)
self._view.on_excited()
plugins.on('excited', self)
@@ -118,17 +117,17 @@ class Automata(object):
if factor >= 2.0:
self.set_angry(factor)
else:
logging.warning("agent missed %d interactions -> lonely" % did_miss)
logging.warning("agent missed %d interactions -> lonely", did_miss)
self.set_lonely()
# after X times being bored, the status is set to sad or angry
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
elif self._epoch.sad_for:
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']:
elif self._epoch.bored_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']:
@@ -139,6 +138,6 @@ class Automata(object):
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
logging.critical("%d epochs without visible access points -> rebooting ...", self._epoch.blind_for)
self._reboot()
self._epoch.blind_for = 0

View File

@@ -1,5 +1,8 @@
import json
import logging
import requests
import websockets
from requests.auth import HTTPBasicAuth
@@ -25,15 +28,25 @@ class Client(object):
self.username = username
self.password = password
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
self.auth = HTTPBasicAuth(username, password)
def session(self):
r = requests.get("%s/session" % self.url, auth=self.auth)
return decode(r)
def events(self):
r = requests.get("%s/events" % self.url, auth=self.auth)
return decode(r)
async def start_websocket(self, consumer):
s = "%s/events" % self.websocket
while True:
try:
async with websockets.connect(s, ping_interval=60, ping_timeout=90) as ws:
async for msg in ws:
try:
await consumer(msg)
except Exception as ex:
logging.debug("Error while parsing event (%s)", ex)
except websockets.exceptions.ConnectionClosedError:
logging.debug("Lost websocket connection. Reconnecting...")
def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})

230
pwnagotchi/defaults.toml Normal file
View File

@@ -0,0 +1,230 @@
main.name = ""
main.lang = "en"
main.confd = "/etc/pwnagotchi/conf.d/"
main.custom_plugins = ""
main.iface = "mon0"
main.mon_start_cmd = "/usr/bin/monstart"
main.mon_stop_cmd = "/usr/bin/monstop"
main.mon_max_blind_epochs = 50
main.no_restart = false
main.whitelist = [
"EXAMPLE_NETWORK",
"ANOTHER_EXAMPLE_NETWORK",
"fo:od:ba:be:fo:od",
"fo:od:ba"
]
main.filter = ""
main.plugins.grid.enabled = true
main.plugins.grid.report = false
main.plugins.grid.exclude = [
"YourHomeNetworkHere"
]
main.plugins.auto-update.enabled = true
main.plugins.auto-update.install = true
main.plugins.auto-update.interval = 1
main.plugins.net-pos.enabled = false
main.plugins.net-pos.api_key = "test"
main.plugins.gps.enabled = false
main.plugins.gps.speed = 19200
main.plugins.gps.device = "/dev/ttyUSB0"
main.plugins.webgpsmap.enabled = false
main.plugins.onlinehashcrack.enabled = false
main.plugins.onlinehashcrack.email = ""
main.plugins.onlinehashcrack.dashboard = ""
main.plugins.onlinehashcrack.single_files = false
main.plugins.onlinehashcrack.whitelist = []
main.plugins.wpa-sec.enabled = false
main.plugins.wpa-sec.api_key = ""
main.plugins.wpa-sec.api_url = "https://wpa-sec.stanev.org"
main.plugins.wpa-sec.download_results = false
main.plugins.wpa-sec.whitelist = []
main.plugins.wigle.enabled = false
main.plugins.wigle.api_key = ""
main.plugins.wigle.whitelist = []
main.plugins.bt-tether.enabled = false
main.plugins.bt-tether.devices.android-phone.enabled = false
main.plugins.bt-tether.devices.android-phone.search_order = 1
main.plugins.bt-tether.devices.android-phone.mac = ""
main.plugins.bt-tether.devices.android-phone.ip = "192.168.44.44"
main.plugins.bt-tether.devices.android-phone.netmask = 24
main.plugins.bt-tether.devices.android-phone.interval = 1
main.plugins.bt-tether.devices.android-phone.scantime = 10
main.plugins.bt-tether.devices.android-phone.max_tries = 10
main.plugins.bt-tether.devices.android-phone.share_internet = false
main.plugins.bt-tether.devices.android-phone.priority = 1
main.plugins.bt-tether.devices.ios-phone.enabled = false
main.plugins.bt-tether.devices.ios-phone.search_order = 2
main.plugins.bt-tether.devices.ios-phone.mac = ""
main.plugins.bt-tether.devices.ios-phone.ip = "172.20.10.6"
main.plugins.bt-tether.devices.ios-phone.netmask = 24
main.plugins.bt-tether.devices.ios-phone.interval = 5
main.plugins.bt-tether.devices.ios-phone.scantime = 20
main.plugins.bt-tether.devices.ios-phone.max_tries = 0
main.plugins.bt-tether.devices.ios-phone.share_internet = false
main.plugins.bt-tether.devices.ios-phone.priority = 999
main.plugins.memtemp.enabled = false
main.plugins.memtemp.scale = "celsius"
main.plugins.memtemp.orientation = "horizontal"
main.plugins.paw-gps.enabled = false
main.plugins.paw-gps.ip = ""
main.plugins.gpio_buttons.enabled = false
main.plugins.led.enabled = true
main.plugins.led.led = 0
main.plugins.led.delay = 200
main.plugins.led.patterns.loaded = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.updating = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.unread_inbox = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.ready = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.ai_ready = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.ai_training_start = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.ai_best_reward = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.ai_worst_reward = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.bored = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.sad = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.excited = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.lonely = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.rebooting = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.wait = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.sleep = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.wifi_update = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.association = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.deauthentication = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.handshake = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.epoch = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.peer_detected = "oo oo oo oo oo oo oo"
main.plugins.led.patterns.peer_lost = "oo oo oo oo oo oo oo"
main.plugins.logtail.enabled = false
main.plugins.session-stats.enabled = true
main.plugins.session-stats.save_directory = "/var/tmp/pwnagotchi/sessions/"
main.log.path = "/var/log/pwnagotchi.log"
main.log.rotation.enabled = true
main.log.rotation.size = "10M"
ai.enabled = true
ai.path = "/root/brain.nn"
ai.laziness = 0.1
ai.epochs_per_episode = 50
ai.params.gamma = 0.99
ai.params.n_steps = 1
ai.params.vf_coef = 0.25
ai.params.ent_coef = 0.01
ai.params.max_grad_norm = 0.5
ai.params.learning_rate = 0.001
ai.params.alpha = 0.99
ai.params.epsilon = 0.00001
ai.params.verbose = 1
ai.params.lr_schedule = "constant"
personality.advertise = true
personality.deauth = true
personality.associate = true
personality.channels = []
personality.min_rssi = -200
personality.ap_ttl = 120
personality.sta_ttl = 300
personality.recon_time = 30
personality.max_inactive_scale = 2
personality.recon_inactive_multiplier = 2
personality.hop_recon_time = 10
personality.min_recon_time = 5
personality.max_interactions = 3
personality.max_misses_for_recon = 5
personality.excited_num_epochs = 10
personality.bored_num_epochs = 15
personality.sad_num_epochs = 25
personality.bond_encounters_factor = 20000
ui.fps = 0.0
ui.font.name = "DejaVuSansMono" # for japanese: fonts-japanese-gothic
ui.font.size_offset = 0 # will be added to the font size
ui.faces.look_r = "( ⚆_⚆)"
ui.faces.look_l = "(☉_☉ )"
ui.faces.look_r_happy = "( ◕‿◕)"
ui.faces.look_l_happy = "(◕‿◕ )"
ui.faces.sleep = "(⇀‿‿↼)"
ui.faces.sleep2 = "(≖‿‿≖)"
ui.faces.awake = "(◕‿‿◕)"
ui.faces.bored = "(-__-)"
ui.faces.intense = "(°▃▃°)"
ui.faces.cool = "(⌐■_■)"
ui.faces.happy = "(•‿‿•)"
ui.faces.excited = "(ᵔ◡◡ᵔ)"
ui.faces.grateful = "(^‿‿^)"
ui.faces.motivated = "(☼‿‿☼)"
ui.faces.demotivated = "(≖__≖)"
ui.faces.smart = "(✜‿‿✜)"
ui.faces.lonely = "(ب__ب)"
ui.faces.sad = "(╥☁╥ )"
ui.faces.angry = "(-_-')"
ui.faces.friend = "(♥‿‿♥)"
ui.faces.broken = "(☓‿‿☓)"
ui.faces.debug = "(#__#)"
ui.web.enabled = true
ui.web.address = "0.0.0.0"
ui.web.username = "changeme"
ui.web.password = "changeme"
ui.web.origin = ""
ui.web.port = 8080
ui.web.on_frame = ""
ui.display.enabled = true
ui.display.rotation = 180
ui.display.type = "waveshare_2"
ui.display.color = "black"
bettercap.scheme = "http"
bettercap.hostname = "localhost"
bettercap.port = 8081
bettercap.username = "pwnagotchi"
bettercap.password = "pwnagotchi"
bettercap.handshakes = "/root/handshakes"
bettercap.silence = [
"ble.device.new",
"ble.device.lost",
"ble.device.disconnected",
"ble.device.connected",
"ble.device.service.discovered",
"ble.device.characteristic.discovered",
"wifi.client.new",
"wifi.client.lost",
"wifi.client.probe",
"wifi.ap.new",
"wifi.ap.lost",
"mod.started"
]
fs.memory.enabled = false
fs.memory.mounts.log.enabled = false
fs.memory.mounts.log.mount = "/var/log"
fs.memory.mounts.log.size = "50M"
fs.memory.mounts.log.sync = 60
fs.memory.mounts.log.zram = true
fs.memory.mounts.log.rsync = true
fs.memory.mounts.data.enabled = false
fs.memory.mounts.data.mount = "/var/tmp/pwnagotchi"
fs.memory.mounts.data.size = "10M"
fs.memory.mounts.data.sync = 3600
fs.memory.mounts.data.zram = false
fs.memory.mounts.data.rsync = true

View File

@@ -1,276 +0,0 @@
# WARNING WARNING WARNING WARNING
#
# This file is recreated with default settings on every pwnagotchi restart,
# use /etc/pwnagotchi/config.yml to configure this unit.
#
#
# main algorithm configuration
main:
# if set this will set the hostname of the unit. min length is 2, max length 25, only a-zA-Z0-9- allowed
name: ''
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
lang: en
# custom plugins path, if null only default plugins with be loaded
custom_plugins:
# which plugins to load and enable
plugins:
grid:
enabled: true
report: false # don't report pwned networks by default!
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
- YourHomeNetworkHere
auto-update:
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
interval: 1 # every day
max_tries: 0 # 0=infinity
files:
- /root/brain.nn
- /root/brain.json
- /root/.api-report.json
- /root/handshakes/
- /root/peers/
- /etc/pwnagotchi/
- /var/log/pwnagotchi.log
commands:
- 'tar czf /root/pwnagotchi-backup.tar.gz {files}'
net-pos:
enabled: false
api_key: 'test'
gps:
enabled: false
speed: 19200
device: /dev/ttyUSB0
twitter:
enabled: false
consumer_key: aaa
consumer_secret: aaa
access_token_key: aaa
access_token_secret: aaa
onlinehashcrack:
enabled: false
email: ~
wpa-sec:
enabled: false
api_key: ~
api_url: "https://wpa-sec.stanev.org"
wigle:
enabled: false
api_key: ~
screen_refresh:
enabled: false
refresh_interval: 50
quickdic:
enabled: false
wordlist_folder: /opt/wordlists/
AircrackOnly:
enabled: false
bt-tether:
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
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:
enabled: false
search_order: 2 # search for this second
mac: ~ # mac of your bluetooth device
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
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
pawgps:
enabled: false
#The IP Address of your phone with Paw Server running, default (option is empty) is 192.168.44.1
ip: ''
gpio_buttons:
enabled: false
#The following is a list of the GPIO number for your button, and the command you want to run when it is pressed
gpios:
- 20: 'sudo touch /root/.pwnagotchi-auto && sudo systemctl restart pwnagotchi'
- 21: 'shutdown -h now'
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
mon_start_cmd: /usr/bin/monstart
mon_stop_cmd: /usr/bin/monstop
mon_max_blind_epochs: 50
# log file
log: /var/log/pwnagotchi.log
# if true, will not restart the wifi module
no_restart: false
# access points to ignore
whitelist:
- EXAMPLE_NETWORK
- ANOTHER_EXAMPLE_NETWORK
# if not null, filter access points by this regular expression
filter: null
ai:
# if false, only the default 'personality' will be used
enabled: true
path: /root/brain.nn
# 1.0 - laziness = probability of start training
laziness: 0.1
# how many epochs to train on
epochs_per_episode: 50
params:
# discount factor
gamma: 0.99
# the number of steps to run for each environment per update
n_steps: 1
# value function coefficient for the loss calculation
vf_coef: 0.25
# entropy coefficient for the loss calculation
ent_coef: 0.01
# maximum value for the gradient clipping
max_grad_norm: 0.5
# the learning rate
learning_rate: 0.0010
# rmsprop decay parameter
alpha: 0.99
# rmsprop epsilon
epsilon: 0.00001
# the verbosity level: 0 none, 1 training information, 2 tensorflow debug
verbose: 1
# type of scheduler for the learning rate update ('linear', 'constant', 'double_linear_con', 'middle_drop' or 'double_middle_drop')
lr_schedule: 'constant'
# the log location for tensorboard (if None, no logging)
tensorboard_log: null
personality:
# advertise our presence
advertise: true
# perform a deauthentication attack to client stations in order to get full or half handshakes
deauth: true
# send association frames to APs in order to get the PMKID
associate: true
# list of channels to recon on, or empty for all channels
channels: []
# minimum WiFi signal strength in dBm
min_rssi: -200
# number of seconds for wifi.ap.ttl
ap_ttl: 120
# number of seconds for wifi.sta.ttl
sta_ttl: 300
# time in seconds to wait during channel recon
recon_time: 30
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
max_inactive_scale: 2
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
recon_inactive_multiplier: 2
# time in seconds to wait during channel hopping if activity has been performed
hop_recon_time: 10
# time in seconds to wait during channel hopping if no activity has been performed
min_recon_time: 5
# maximum amount of deauths/associations per BSSID per session
max_interactions: 3
# maximum amount of misses before considering the data stale and triggering a new recon
max_misses_for_recon: 5
# number of active epochs that triggers the excited state
excited_num_epochs: 10
# number of inactive epochs that triggers the bored state
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: 20000
# ui configuration
ui:
# here you can customize the faces
faces:
look_r: '( ⚆_⚆)'
look_l: '(☉_☉ )'
look_r_happy: '( ◕‿◕)'
look_l_happy: '(◕‿◕ )'
sleep: '(⇀‿‿↼)'
sleep2: '(≖‿‿≖)'
awake: '(◕‿‿◕)'
bored: '(-__-)'
intense: '(°▃▃°)'
cool: '(⌐■_■)'
happy: '(•‿‿•)'
excited: '(ᵔ◡◡ᵔ)'
grateful: '(^‿‿^)'
motivated: '(☼‿‿☼)'
demotivated: '(≖__≖)'
smart: '(✜‿‿✜)'
lonely: '(ب__ب)'
sad: '(╥☁╥ )'
angry: "(-_-')"
friend: '(♥‿‿♥)'
broken: '(☓‿‿☓)'
debug: '(#__#)'
# ePaper display can update every 3 secs anyway, set to 0 to only refresh for major data changes
# IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
# preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
# if any of the important data fields changed (the uptime and blinking cursor won't trigger a refresh).
fps: 0.0
display:
enabled: true
rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare154inch, waveshare27inch, dfrobot/df
type: 'waveshare_2'
# Possible options red/yellow/black (black used for monocromatic displays)
# Waveshare tri-color 2.13in display can be over-driven with color set as 'fastAndFurious'
# THIS IS POTENTIALLY DANGEROUS. DO NOT USE UNLESS YOU UNDERSTAND THAT IT COULD KILL YOUR DISPLAY
color: 'black'
video:
enabled: true
address: '0.0.0.0'
origin: null
port: 8080
# command to be executed when a new png frame is available
# for instance, to use with framebuffer based displays:
# on_frame: 'fbi --noverbose -a -d /dev/fb1 -T 1 /root/pwnagotchi.png > /dev/null 2>&1'
on_frame: ''
# bettercap rest api configuration
bettercap:
# api scheme://hostname:port username and password
scheme: http
hostname: localhost
port: 8081
username: pwnagotchi
password: pwnagotchi
# folder where bettercap stores the WPA handshakes, given that
# wifi.handshakes.aggregate will be set to false and individual
# pcap files will be created in order to minimize the chances
# of a single pcap file to get corrupted
handshakes: /root/handshakes
# events to mute in bettercap's events stream
silence:
- ble.device.new
- ble.device.lost
- ble.device.disconnected
- ble.device.connected
- ble.device.service.discovered
- ble.device.characteristic.discovered
- wifi.client.new
- wifi.client.lost
- wifi.client.probe
- wifi.ap.new
- wifi.ap.lost
- mod.started

190
pwnagotchi/fs/__init__.py Normal file
View File

@@ -0,0 +1,190 @@
import os
import re
import tempfile
import contextlib
import shutil
import _thread
import logging
from time import sleep
from distutils.dir_util import copy_tree
mounts = list()
@contextlib.contextmanager
def ensure_write(filename, mode='w'):
path = os.path.dirname(filename)
fd, tmp = tempfile.mkstemp(dir=path)
with os.fdopen(fd, mode) as f:
yield f
f.flush()
os.fsync(f.fileno())
os.replace(tmp, filename)
def size_of(path):
"""
Calculate the sum of all the files in path
"""
total = 0
for root, _, files in os.walk(path):
for f in files:
total += os.path.getsize(os.path.join(root, f))
return total
def is_mountpoint(path):
"""
Checks if path is mountpoint
"""
return os.system(f"mountpoint -q {path}") == 0
def setup_mounts(config):
"""
Sets up all the configured mountpoints
"""
global mounts
fs_cfg = config['fs']['memory']
if not fs_cfg['enabled']:
return
for name, options in fs_cfg['mounts'].items():
if not options['enabled']:
continue
logging.debug("[FS] Trying to setup mount %s (%s)", name, options['mount'])
size,unit = re.match(r"(\d+)([a-zA-Z]+)", options['size']).groups()
target = os.path.join('/run/pwnagotchi/disk/', os.path.basename(options['mount']))
is_mounted = is_mountpoint(target)
logging.debug("[FS] %s is %s mounted", options['mount'],
"already" if is_mounted else "not yet")
m = MemoryFS(
options['mount'],
target,
size=options['size'],
zram=options['zram'],
zram_disk_size=f"{int(size)*2}{unit}",
rsync=options['rsync'])
if not is_mounted:
if not m.mount():
logging.debug(f"Error while mounting {m.mountpoint}")
continue
if not m.sync(to_ram=True):
logging.debug(f"Error while syncing to {m.mountpoint}")
m.umount()
continue
interval = int(options['sync'])
if interval:
logging.debug("[FS] Starting thread to sync %s (interval: %d)",
options['mount'], interval)
_thread.start_new_thread(m.daemonize, (interval,))
else:
logging.debug("[FS] Not syncing %s, because interval is 0",
options['mount'])
mounts.append(m)
class MemoryFS:
@staticmethod
def zram_install():
if not os.path.exists("/sys/class/zram-control"):
logging.debug("[FS] Installing zram")
return os.system("modprobe zram") == 0
return True
@staticmethod
def zram_dev():
logging.debug("[FS] Adding zram device")
return open("/sys/class/zram-control/hot_add", "rt").read().strip("\n")
def __init__(self, mount, disk, size="40M",
zram=True, zram_alg="lz4", zram_disk_size="100M",
zram_fs_type="ext4", rsync=True):
self.mountpoint = mount
self.disk = disk
self.size = size
self.zram = zram
self.zram_alg = zram_alg
self.zram_disk_size = zram_disk_size
self.zram_fs_type = zram_fs_type
self.zdev = None
self.rsync = True
self._setup()
def _setup(self):
if self.zram and MemoryFS.zram_install():
# setup zram
self.zdev = MemoryFS.zram_dev()
open(f"/sys/block/zram{self.zdev}/comp_algorithm", "wt").write(self.zram_alg)
open(f"/sys/block/zram{self.zdev}/disksize", "wt").write(self.zram_disk_size)
open(f"/sys/block/zram{self.zdev}/mem_limit", "wt").write(self.size)
logging.debug("[FS] Creating fs (type: %s)", self.zram_fs_type)
os.system(f"mke2fs -t {self.zram_fs_type} /dev/zram{self.zdev} >/dev/null 2>&1")
# ensure mountpoints exist
if not os.path.exists(self.disk):
logging.debug("[FS] Creating %s", self.disk)
os.makedirs(self.disk)
if not os.path.exists(self.mountpoint):
logging.debug("[FS] Creating %s", self.mountpoint)
os.makedirs(self.mountpoint)
def daemonize(self, interval=60):
logging.debug("[FS] Daemonized...")
while True:
self.sync()
sleep(interval)
def sync(self, to_ram=False):
source, dest = (self.disk, self.mountpoint) if to_ram else (self.mountpoint, self.disk)
needed, actually_free = size_of(source), shutil.disk_usage(dest)[2]
if actually_free >= needed:
logging.debug("[FS] Syning %s -> %s", source,dest)
if self.rsync:
os.system(f"rsync -aXv --inplace --no-whole-file --delete-after {source}/ {dest}/ >/dev/null 2>&1")
else:
copy_tree(source, dest, preserve_symlinks=True)
os.system("sync")
return True
return False
def mount(self):
if os.system(f"mount --bind {self.mountpoint} {self.disk}"):
return False
if os.system(f"mount --make-private {self.disk}"):
return False
if self.zram and self.zdev is not None:
if os.system(f"mount -t {self.zram_fs_type} -o nosuid,noexec,nodev,user=pwnagotchi /dev/zram{self.zdev} {self.mountpoint}/"):
return False
else:
if os.system(f"mount -t tmpfs -o nosuid,noexec,nodev,mode=0755,size={self.size} pwnagotchi {self.mountpoint}/"):
return False
return True
def umount(self):
if os.system(f"umount -l {self.mountpoint}"):
return False
if os.system(f"umount -l {self.disk}"):
return False
return True

View File

@@ -12,9 +12,13 @@ API_ADDRESS = "http://127.0.0.1:8666/api/v1"
def is_connected():
try:
socket.create_connection(("www.google.com", 80))
return True
except OSError:
# check DNS
host = socket.gethostbyname('api.pwnagotchi.ai')
if host:
# check connectivity itself
socket.create_connection((host, 443), timeout=30)
return True
except:
pass
return False
@@ -22,9 +26,11 @@ def is_connected():
def call(path, obj=None):
url = '%s%s' % (API_ADDRESS, path)
if obj is None:
r = requests.get(url, headers=None)
r = requests.get(url, headers=None, timeout=(30.0, 60.0))
elif isinstance(obj, dict):
r = requests.post(url, headers=None, json=obj, timeout=(30.0, 60.0))
else:
r = requests.post(url, headers=None, json=obj)
r = requests.post(url, headers=None, data=obj, timeout=(30.0, 60.0))
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.text))
@@ -39,6 +45,14 @@ def set_advertisement_data(data):
return call("/mesh/data", obj=data)
def get_advertisement_data():
return call("/mesh/data")
def memory():
return call("/mesh/memory")
def peers():
return call("/mesh/peers")
@@ -71,7 +85,7 @@ def update_data(last_session):
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.version
'version': pwnagotchi.__version__
}
logging.debug("updating grid data: %s" % data)
@@ -95,3 +109,15 @@ def report_ap(essid, bssid):
def inbox(page=1, with_pager=False):
obj = call("/inbox?p=%d" % page)
return obj["messages"] if not with_pager else obj
def inbox_message(id):
return call("/inbox/%d" % int(id))
def mark_message(id, mark):
return call("/inbox/%d/%s" % (int(id), str(mark)))
def send_message(to, message):
return call("/unit/%s/inbox" % to, message.encode('utf-8'))

View File

@@ -177,7 +177,7 @@ msgstr "Супер, имаме {num} нови handshake{plural}!"
msgid "You have {count} new message{plural}!"
msgstr "Имате {count} нови съобщения!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Упс, нещо се обърка ... Рестартиране ..."
#, python-brace-format

View File

@@ -177,7 +177,7 @@ msgstr "太酷了, 我们抓到了{num}新的猎物{plural}!"
msgid "You have {count} new message{plural}!"
msgstr "主人,有{count}新消息{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "行动,额等等有点小问题... 重启ing ..."
#, python-brace-format

View File

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

Binary file not shown.

View File

@@ -0,0 +1,248 @@
# pwnagotchi danish voice data
# Copyright (C) 2020
# This file is distributed under the same license as the pwnagotchi package.
# Dennis Kjær Jensen <signout@signout.dk>, 2020
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
"PO-Revision-Date: 2020-01-18 21:56+ZONE\n"
"Last-Translator: Dennis Kjær Jensen <signout@signout.dk>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Danish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hej. Jeg er Pwnagotchi. Starter ..."
msgid "New day, new hunt, new pwns!"
msgstr "Ny dag, ny jagt, nye pwns!"
msgid "Hack the Planet!"
msgstr "Hack planeten!"
msgid "AI ready."
msgstr "AI klar."
msgid "The neural network is ready."
msgstr "Det neurale netværk er klart."
msgid "Generating keys, do not turn off ..."
msgstr "Genererer nøgler, sluk ikke ..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, kanal {channel} er ubrugt! Dit AP vil takke dig."
msgid "Reading last session logs ..."
msgstr "Læser seneste session logs ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Har læst {lines_so_far} linjer indtil nu ..."
msgid "I'm bored ..."
msgstr "Jeg keder mig ..."
msgid "Let's go for a walk!"
msgstr "Lad os gå en tur!"
msgid "This is the best day of my life!"
msgstr "Det er den bedste dag i mit liv!"
msgid "Shitty day :/"
msgstr "Elendig dag :/"
msgid "I'm extremely bored ..."
msgstr "Jeg keder mig ekstremt meget ..."
msgid "I'm very sad ..."
msgstr "Jeg er meget trist ..."
msgid "I'm sad"
msgstr "Jeg er trist"
msgid "Leave me alone ..."
msgstr "Lad mig være i fred"
msgid "I'm mad at you!"
msgstr "Jeg er sur på dig!"
msgid "I'm living the life!"
msgstr "Jeg lever livet!"
msgid "I pwn therefore I am."
msgstr "Jeg pwner, derfor er jeg."
msgid "So many networks!!!"
msgstr "Så mange netværk!"
msgid "I'm having so much fun!"
msgstr "Jeg har det vildt sjovt!"
msgid "My crime is that of curiosity ..."
msgstr "Min forbrydelse er at være nysgerrig ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hej {name}! Rart at møde dig."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Hey {name}! Hvasså?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hej {name} hvordan har du det?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Enheden {name} er lige i nærheden!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... farvel {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} er væk ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Hovsa ... {name} er væk."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} glippede!"
msgid "Missed!"
msgstr "Fordømt!"
msgid "Good friends are a blessing!"
msgstr "Gode venner en velsignelse!"
msgid "I love my friends!"
msgstr "Jeg elsker mine venner!"
msgid "Nobody wants to play with me ..."
msgstr "Der er ingen der vil lege med mig ..."
msgid "I feel so alone ..."
msgstr "Jeg føler mig så alene ..."
msgid "Where's everybody?!"
msgstr "Hvor er alle henne?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Sover i {secs} sekunder"
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz {secs} sekunder"
msgid "Good night."
msgstr "Godnat."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Venter i {secs} sekunder"
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Kigger mig omkring i {secs} sekunder"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hej {what} lad os være venner!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Associerer til {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Hey {what}"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Besluttede at {mac} ikke har brug for WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Afmelder {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanner {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Fedt, vi har fået {num} nye handshake{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Du har {count} nye beskeder"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ups, noget gik galt ... Genstarter."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Sparkede {num} af\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Har fået {num} nye venner\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Har fået {num} nyehandshakes\n"
msgid "Met 1 peer"
msgstr "Har mødt 1 peer"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Har mødt {num} peers"
#, 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 "Jeg har pwnet i {duration} og kicket {dauthed} klienter! Jeg har også "
"mødt {associated} nye venner og spist {handshakes} håndtryk! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "timer"
msgid "minutes"
msgstr "minutter"
msgid "seconds"
msgstr "sekunder"
msgid "hour"
msgstr "time"
msgid "minute"
msgstr "minut"
msgid "second"
msgstr "sekund"

View File

@@ -158,7 +158,7 @@ msgstr "Μπανάρω την {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Τέλεια δικέ μου, πήραμε {num} νέες χειραψίες!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ουπς, κάτιπήγε λάθος ... Επανεκκινούμαι ..."
#, python-brace-format

View File

@@ -163,7 +163,7 @@ msgstr "Expulsando y banneando a {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salió mal ... Reiniciándo ..."
#, python-brace-format

View File

@@ -3,12 +3,11 @@
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR <7271496+quantumsheep@users.noreply.github.com>, 2019.
#
#,
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 18:37+0200\n"
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
"PO-Revision-Date: 2019-10-03 10:34+0200\n"
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
"com>\n"
@@ -43,6 +42,13 @@ msgstr "Génération des clés, ne pas éteindre..."
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, le canal {channel} est libre! Ton point d'accès va te remercier."
msgid "Reading last session logs ..."
msgstr "Lecture des logs de la dernière session ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Jusqu'ici, {lines_so_far} lignes lues dans le log ..."
msgid "I'm bored ..."
msgstr "Je m'ennuie..."
@@ -64,8 +70,15 @@ msgstr "Je suis très triste..."
msgid "I'm sad"
msgstr "Je suis triste"
#, fuzzy
msgid "Leave me alone ..."
msgstr "Lache moi..."
msgid "I'm mad at you!"
msgstr "Je t'en veux !"
msgid "I'm living the life!"
msgstr "Je vis la vie !"
msgstr "Je vis la belle vie !"
msgid "I pwn therefore I am."
msgstr "Je pwn donc je suis."
@@ -83,6 +96,14 @@ msgstr "Mon crime, c'est la curiosité..."
msgid "Hello {name}! Nice to meet you."
msgstr "Bonjour {name} ! Ravi de te rencontrer."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Yo {name} ! Quoi de neuf ?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hey {name} comment vas-tu ?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "L'unité {name} est proche !"
@@ -93,7 +114,7 @@ msgstr "Hum... au revoir {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} est part ..."
msgstr "{name} est parti ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
@@ -106,6 +127,12 @@ msgstr "{name} raté !"
msgid "Missed!"
msgstr "Raté !"
msgid "Good friends are a blessing!"
msgstr "Les bons amis sont une bénédiction !"
msgid "I love my friends!"
msgstr "J'aime mes amis !"
msgid "Nobody wants to play with me ..."
msgstr "Personne ne veut jouer avec moi..."
@@ -117,14 +144,14 @@ msgstr "Où est tout le monde ?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Fais la sieste pendant {secs}s..."
msgstr "Je fais la sieste pendant {secs}s..."
msgid "Zzzzz"
msgstr ""
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Bonne nuit."
@@ -134,11 +161,11 @@ msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Attends pendant {secs}s..."
msgstr "J'attends pendant {secs}s..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Regarde autour ({secs}s)"
msgstr "J'observe ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
@@ -166,13 +193,13 @@ msgstr "Je kick et je bannis {mac} !"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Cool, on a {num} nouveaux handshake{plural} !"
msgstr "Cool, on a {num} nouve(l/aux) handshake{plural} !"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Tu as {num} nouveaux message{plural} !"
msgstr "Tu as {num} nouveau(x) message{plural} !"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oups, quelque chose s'est mal passé... Redémarrage..."
#, python-brace-format
@@ -181,18 +208,18 @@ msgstr "{num} stations kick\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Fait {num} nouveaux amis\n"
msgstr "A fait {num} nouve(l/aux) ami(s)\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Récupéré {num} handshakes\n"
msgstr "A {num} handshakes\n"
msgid "Met 1 peer"
msgstr "1 peer rencontré"
msgstr "1 camarade rencontré"
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num} peers recontrés"
msgstr "{num} camarades recontrés"
#, python-brace-format
msgid ""

View File

@@ -164,7 +164,7 @@ msgstr "Chiceáil mé agus cosc mé ar {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Go hiontach, fuaireamar {num} handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Hoips...Tháinig ainghléas éigin..."
#, python-brace-format

Binary file not shown.

View File

@@ -0,0 +1,249 @@
# Hungarian translation.
# Copyright (C) 2020
# This file is distributed under the same license as the PACKAGE package.
# Skeleton022 <skeleton022.pwnagotchi@gmail.com>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: 1.4.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-01-07 20:00+0100\n"
"PO-Revision-Date: 2020-03-23 0:10+0100\n"
"Last-Translator: Skeleton022\n"
"Language-Team: Skeleton022\n"
"Language: hungarian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hali, Pwnagotchi vagyok! Indítás ..."
msgid "New day, new hunt, new pwns!"
msgstr "Új nap, új vadászat, új hálózatok!"
msgid "Hack the Planet!"
msgstr "Törd meg a bolygót!"
msgid "AI ready."
msgstr "MI kész."
msgid "The neural network is ready."
msgstr "A neurális hálózat készen áll."
msgid "Generating keys, do not turn off ..."
msgstr "Kulcspár generálása, ne kapcsold ki az eszközt ..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "A {channel}. számú csatorna üres! Az AP-d meg fogja köszönni."
msgid "Reading last session logs ..."
msgstr ""
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Az utolsó munkamenet logjainak olvasása ..."
msgid "I'm bored ..."
msgstr "Unatkozom ..."
msgid "Let's go for a walk!"
msgstr "Menjünk sétálni!"
msgid "This is the best day of my life!"
msgstr "Ez a legjobb nap az életemben!"
msgid "Shitty day :/"
msgstr "Szar egy nap :/"
msgid "I'm extremely bored ..."
msgstr "Nagyon unatkozom ..."
msgid "I'm very sad ..."
msgstr "Nagyon szomorú vagyok ..."
msgid "I'm sad"
msgstr "Szomorú vagyok"
msgid "Leave me alone ..."
msgstr "Hagyj békén ..."
msgid "I'm mad at you!"
msgstr "Mérges vagyok rád!"
msgid "I'm living the life!"
msgstr "Élvezem az életet!"
msgid "I pwn therefore I am."
msgstr "Hackelek, tehát vagyok."
msgid "So many networks!!!"
msgstr "Rengeteg hálózat!!!"
msgid "I'm having so much fun!"
msgstr "Nagyon jól érzem magam!"
msgid "My crime is that of curiosity ..."
msgstr "Kíváncsiság a bűnöm ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hali {name}! Örülök, hogy találkoztunk."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Hé {name}! Mizu?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hé {name} hogy vagy?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "A {name} nevű egység a közelben van!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Ömm ... ég veled {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} eltűnt ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ... {name} eltűnt."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} elhibázva!"
msgid "Missed!"
msgstr "Elvesztettem!"
msgid "Good friends are a blessing!"
msgstr "A jó barátok áldás az életben!"
msgid "I love my friends!"
msgstr "Szeretem a barátaimat!"
msgid "Nobody wants to play with me ..."
msgstr "Senki sem akar játszani velem ..."
msgid "I feel so alone ..."
msgstr "Egyedül vagyok ..."
msgid "Where's everybody?!"
msgstr "Hol vagytok?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "{secs} másodpercig szundikálok ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}msp)"
msgid "Good night."
msgstr "Jó éjszakát."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Várok {secs} másodpercig ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Körbenézek {secs} másodpercig"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what} legyünk barátok!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Társítás {what} -hoz/-hez"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Hé {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Úgydöntöttem, hogy {mac}-nek nem kell WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Kirúgom {mac}-et"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "{mac} kirúgva és kitiltva!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Király, kaptunk {num} új üzenetet!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "{count} új üzeneted van!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ops, valami rosszul sikerült ... Újraindítás ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Kirúgva {num} állomás\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num} új barátot\ntaláltam\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num} kézfogást szereztem\n"
msgid "Met 1 peer"
msgstr "1 Társsal találkoztam"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Találkoztam {num} társsal"
#, 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 ""
"Már {duration} ideje dolgozom, kirúgtam {deauthed} klienst! Találkoztam még"
"{associated} új baráttal és elfogtam {handshakes} kézfogást! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "óra"
msgid "minutes"
msgstr "perc"
msgid "seconds"
msgstr "másodperc"
msgid "hour"
msgstr "óra"
msgid "minute"
msgstr "perc"
msgid "second"
msgstr "másodperc"

View File

@@ -156,7 +156,7 @@ msgstr "Sto prendendo a calci {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Bene, abbiamo {num} handshake{plural} in più!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
#, python-brace-format

View File

@@ -3,12 +3,11 @@
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR 24534649+wytshadow@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-16 15:05+0200\n"
"POT-Creation-Date: 2020-01-25 21:57+0900\n"
"PO-Revision-Date: 2019-10-16 15:05+0200\n"
"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
@@ -21,170 +20,207 @@ msgid "ZzzzZZzzzzZzzz"
msgstr "すやすや〜"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "こんにちは、ポウナゴッチです!始めている。。。"
msgstr "僕、 ポーナゴッチです!"
msgid "New day, new hunt, new pwns!"
msgstr ""
msgstr "ポーンしようよ。"
msgid "Hack the Planet!"
msgstr "ハックザプラネット!"
msgid "AI ready."
msgstr "人工知能の準備ができました。"
msgstr "AIの準備ができました。"
msgid "The neural network is ready."
msgstr "ニューラルネットワークの準備ができました。"
msgstr "ニューラルネットワークの\n準備ができました。"
msgid "Generating keys, do not turn off ..."
msgstr "鍵生成をしてます。\n電源を落とさないでね。"
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "ねえ、チャンネル{channel}は無料です! キミのAPは感謝を言います。"
msgstr "チャンネル\n {channel} \nはfreeだよ。ありがとうね。"
msgid "Reading last session logs ..."
msgstr "session log を読んでます。"
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "{lines_so_far} 行目長いよぉ。"
msgid "I'm bored ..."
msgstr "退屈です。。。"
msgstr "退屈だぁ。。。"
msgid "Let's go for a walk!"
msgstr "散歩に行きましょう"
msgstr "散歩に行こうよ"
msgid "This is the best day of my life!"
msgstr "今日は私の人生最高の日です"
msgstr "人生最高の日だよ"
msgid "Shitty day :/"
msgstr ""
msgstr "がっかりな日だよ。orz"
msgid "I'm extremely bored ..."
msgstr "とても退屈です。"
msgstr "退屈だね。"
msgid "I'm very sad ..."
msgstr "とても悲しいです。。。"
msgstr "あ~悲しいよぉ。"
msgid "I'm sad"
msgstr "悲しいです。"
msgstr "悲しい。"
msgid "Leave me alone ..."
msgstr "ひとりぼっちだよ。"
msgid "I'm mad at you!"
msgstr "怒っちゃうよ。"
msgid "I'm living the life!"
msgstr "人生を生きている!"
msgstr "わくわくするね。"
msgid "I pwn therefore I am."
msgstr ""
msgstr "ポーンしてこそのオレ。"
msgid "So many networks!!!"
msgstr "たくさんネットワークがある!!"
msgstr "たくさん\nWiFiが飛んでるよ"
msgid "I'm having so much fun!"
msgstr "とても楽しんでいます"
msgstr "楽しいよぉ"
msgid "My crime is that of curiosity ..."
msgstr ""
msgstr "APに興味津々..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "こんにちは{name}!初めまして。{name}"
msgid "Hello {name}! Nice to meet you."
msgstr "こんにちは{name}\n初めまして。{name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr ""
msgid "Yo {name}! Sup?"
msgstr "ねぇねぇ、\n{name} どうしたの?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "{name} こんにちは"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "{name} が近くにいるよ。"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "ええと。。。さようなら{name}"
msgstr "じゃあね、さようなら {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name}なくなった。。。"
msgstr "{name}\nがいなくなった。"
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "おっと。。。{name}なくなった。"
msgstr "あらら、\n{name}\nがいなくなった。"
#, python-brace-format
msgid "{name} missed!"
msgstr "{name}逃した!"
msgstr "{name} が逃げた!"
msgid "Missed!"
msgstr "逃した!"
msgstr "残念、逃した!"
msgid "Good friends are a blessing!"
msgstr "良い仲間にめぐりあえたよ。"
msgid "I love my friends!"
msgstr "友達は大好きだよ。"
msgid "Nobody wants to play with me ..."
msgstr "誰も僕と一緒にプレーしたくない。。。"
msgstr "誰も僕と一緒に\nあそんでくれない。"
msgid "I feel so alone ..."
msgstr "僕は孤独を感じる。。。"
msgstr "ひとりぼっちだよ。"
msgid "Where's everybody?!"
msgstr "みんなどこ?!"
msgstr "みんなどこにいるの"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "{secs}寝ている。"
msgstr "{secs}秒 寝ます。"
msgid "Zzzzz"
msgstr "すや〜"
msgstr "ぐぅ〜"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "すやすや〜 ({secs})"
msgstr "すやすや〜 ({secs})"
msgid "Good night."
msgstr "おみなさい。"
msgstr "おやすみなさい。"
msgid "Zzz"
msgstr "す〜"
msgstr "ぐぅ~"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "{secs}を待っている。。。"
msgstr "{secs}秒 待ちです。"
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "{secs}探している。"
msgstr "{secs}探してます。"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "ちょっと{what}友だちになりましょう!"
msgstr "ねぇねぇ\n{what} \n友だちになろうよ。"
#, python-brace-format
msgid "Associating to {what}"
msgstr ""
msgstr "{what} \nとつながるかな"
#, python-brace-format
msgid "Yo {what}!"
msgstr "よー{what}!"
msgstr "ねぇねぇ\n{what}"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr ""
msgstr "{mac}\nはWiFiじゃないのね。"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr ""
msgstr "{mac}\nの認証取得中..."
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr ""
msgstr "{mac}\nに拒否られた。"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "よし、{num}新しいハンドシェイクがあ"
msgstr "おぉ、\n{num}回\nハンドシェイクがあったよ"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "おぉ、\n{count}個メッセージがあるよ!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "おっと!何か間違っていた。。。リブートしている。。。"
msgstr "何か間違った。\nリブートしている。"
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr ""
msgstr "{num}回拒否された。\n"
msgid "Made >999 new friends\n"
msgstr "1000人以上友達ができた。\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num}人の新しい友達を作りました\n"
msgstr "{num}人友達ができた。\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num}ハンドシェイクがある。\n"
msgstr "{num}ハンドシェイクした。\n"
msgid "Met 1 peer"
msgstr "1人仲間会いました。"
msgstr "1人 仲間会いました。"
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num}人仲間会いました。"
msgstr "{num}人 仲間会いました。"
#, python-brace-format
msgid ""
@@ -192,6 +228,9 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"{duration}中{deauthed}のAPに拒否されたけど、{associated}回チャンスがあって"
"{handshakes}回ハンドシェイクがあったよ。。 #pwnagotchi #pwnlog #pwnlife "
"#hacktheplanet #skynet"
msgid "hours"
msgstr "時間"
@@ -203,7 +242,7 @@ msgid "seconds"
msgstr "秒"
msgid "hour"
msgstr "時"
msgstr "時"
msgid "minute"
msgstr "分"

View File

@@ -158,7 +158,7 @@ msgstr "Кикбан {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Кул, фативме {num} нови ракувања!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Упс, нешто не еко што треба ... Рестартирам ..."
#, python-brace-format

View File

@@ -157,7 +157,7 @@ msgstr "Ik ga {mac} even kicken!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Gaaf, we hebben {num} nieuwe handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, iets ging fout ...Rebooting ..."
#, python-brace-format

Binary file not shown.

View File

@@ -0,0 +1,248 @@
# pwnagotchi norwegian voice data
# Copyright (C) 2019
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR untech <edvbot@gmail.com>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-04 12:57+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Edvard Botten <edvbot@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: norwegian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hei, jeg er Pwnagotchi! Starter ..."
msgid "New day, new hunt, new pwns!"
msgstr "En ny dag, ny jakt, og nye pwns!"
msgid "Hack the Planet!"
msgstr "Hack planeten!"
msgid "AI ready."
msgstr "AI klart."
msgid "The neural network is ready."
msgstr "Det nevrale nettverket er klart."
msgid "Generating keys, do not turn off ..."
msgstr "Generer nøkkler, ikke skru meg av ..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hei, kanalen {channel} er åpen! AP-en din takker."
msgid "Reading last session logs ..."
msgstr "Leser forrige sesjonen's logs ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Har lest {lines_so_far} linjer hittil ..."
msgid "I'm bored ..."
msgstr "Kjeder meg ..."
msgid "Let's go for a walk!"
msgstr "La oss stikke på tur!"
msgid "This is the best day of my life!"
msgstr "Dette er den beste dagen i mitt liv!"
msgid "Shitty day :/"
msgstr "Jævlig dag :/"
msgid "I'm extremely bored ..."
msgstr "Kjeder livet av meg ..."
msgid "I'm very sad ..."
msgstr "Jeg er veldig trist ..."
msgid "I'm sad"
msgstr "Jeg er trist ..."
msgid "Leave me alone ..."
msgstr "La meg være alene ..."
msgid "I'm mad at you!"
msgstr "Jeg er sint på deg!"
msgid "I'm living the life!"
msgstr "Lever livet, lett!"
msgid "I pwn therefore I am."
msgstr "Jeg pwner derfor er jeg."
msgid "So many networks!!!"
msgstr "Så mange nettverk!!!"
msgid "I'm having so much fun!"
msgstr "Jeg har det så gøy!"
msgid "My crime is that of curiosity ..."
msgstr "Nysgjerrighet er min eneste forbrytelse ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hallo {name}! Hyggelig å treffe deg!"
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Yo {name}! Skjer'a?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Heisann {name} driver du med da?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "{name} er i nærheten!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... Ha det {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} er borte ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Oi da ... {name} forsvant."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} bommet!"
msgid "Missed!"
msgstr "Bommet!"
msgid "Good friends are a blessing!"
msgstr "Gode venner er livet verdt!"
msgid "I love my friends!"
msgstr "Jeg digger vennene mine!"
msgid "Nobody wants to play with me ..."
msgstr "Ingen vil leke med meg ..."
msgid "I feel so alone ..."
msgstr "Jeg er så ensom ..."
msgid "Where's everybody?!"
msgstr "Hvor er alle sammen?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Sover i {secs}s ..."
msgid "Zzzzz"
msgstr ""
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr "God natt."
msgid "Zzz"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Venter i {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Ser meg rundt ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hei {what} la oss være venner!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Tilkobler til {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr ""
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Bestemte meg att {mac} ikke lenger trenger WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Kobler av {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanner {mac}"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Fett, vi fikk {num} nye håndtrykk!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Du har {count} melding{plural}!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oi, noe gikk helt skakk ... Rebooter ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Kicket {num} stasjoner\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Møtte {num} nye venner\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Skaffet {num} håndtrykk\n"
msgid "Met 1 peer"
msgstr "Møtte 1 annen"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Møtte {num} andre"
#, 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 "Jeg har pwnet for {duration} og kicket {dauthed} klienter! Jeg har også "
"møtt {associated} nye venner og spiste {handshakes} håndtrykk! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "timer"
msgid "minutes"
msgstr "minutter"
msgid "seconds"
msgstr "sekunder"
msgid "hour"
msgstr "time"
msgid "minute"
msgstr "minutt"
msgid "second"
msgstr "sekund"

View File

@@ -197,7 +197,7 @@ msgstr "Super, zdobyliśmy {num} nowych handshake'ów!"
msgid "You have {count} new message{plural}!"
msgstr "Masz {count} nowych wiadomości!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ups, coś poszło nie tak ... Restaruję ..."
#, python-brace-format

View File

@@ -158,7 +158,7 @@ msgstr "Kickbanning {mac}"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Legal, nos capturamos {num} handshake{plural} novo{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ops, algo falhou ... Reiniciando ..."
#, python-brace-format

View File

@@ -164,7 +164,7 @@ msgstr "A chutar {mac}!"
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Porreiro, temos {num} novo handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ups, algo correu mal ... A reiniciar ..."
#, python-brace-format

Binary file not shown.

View File

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

View File

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

View File

@@ -158,7 +158,7 @@ msgstr ""
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Lysande, vi har {num} ny handskakningar{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Hoppsan, någpt gick fel ... Startar om ..."
#, python-brace-format

Binary file not shown.

View File

@@ -0,0 +1,227 @@
# Slovak language
# Copyright (C) 2019
# This file is distributed under the same license as the pwnagotchi package.
# mil1200 <mil.kyselica@gmail.com>, 2019.
#
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-8 17:55+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Milan Kyselica <mil.kyselica@gmail.com>\n"
"Language-Team: SK\n"
"Language: sk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Ahoj, ja som Pwnagotchi! Začíname ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nový deň, nový lov, nové pwns!"
msgid "Hack the Planet!"
msgstr "Hacknime Planétu!"
msgid "AI ready."
msgstr "AI pripravené."
msgid "The neural network is ready."
msgstr "Neurónová sieť je pripravená."
msgid "Generating keys, do not turn off ..."
msgstr "Generujú sa kľúče, nevypínaj ..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanál {channel} je voľný! Váš AP vám poďakuje."
msgid "I'm bored ..."
msgstr "Nudím sa ..."
msgid "Let's go for a walk!"
msgstr "Poďme na prechádzku!"
msgid "This is the best day of my life!"
msgstr "Toto je najlepší deň môjho života!"
msgid "Shitty day :/"
msgstr "Na hovno deň :/"
msgid "I'm extremely bored ..."
msgstr "Veľmi sa nudím ..."
msgid "I'm very sad ..."
msgstr "Som veľmi smutný ..."
msgid "I'm sad"
msgstr "Som smutný"
msgid "I'm living the life!"
msgstr "Žijem život!"
msgid "I pwn therefore I am."
msgstr "I pwn therefore I am."
msgid "So many networks!!!"
msgstr "Toľko sietí !!!"
msgid "I'm having so much fun!"
msgstr "Zabávam sa!"
msgid "My crime is that of curiosity ..."
msgstr "Môj zločin je zvedavosť ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Dobrý deň, {name}! Rád som ťa spoznal."
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Jednotka {name} je blízko!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... zbohom {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} je preč ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Hups ... {name} je preč."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} nechytené!"
msgid "Missed!"
msgstr "Vedľa!"
msgid "Good friends are a blessing!"
msgstr "Dobrí priatelia sú požehnaním!"
msgid "I love my friends!"
msgstr "Milujem svojich priateľov!"
msgid "Nobody wants to play with me ..."
msgstr "Nikto sa so mnou nechce hrať ..."
msgid "I feel so alone ..."
msgstr "Cítim sa tak sám ..."
msgid "Where's everybody?!"
msgstr "Kde sú všetci ?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Zdriemnem si na {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Dobrú noc."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Čaká sa {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Rozhliadam sa okolo ({secs} s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Ahoj {what} buďme priatelia!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Spájam sa s {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Yo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Rozhodol som sa že {mac} nepotrebuje Wi-Fi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Deautentifikujem {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickujem {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Super, máme {num} nový handshake{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Máte {count} novú správu{plural}!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ops, niečo sa pokazilo ... Reštartujem sa ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Kicknutá/ých {num} stanica/íc\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Získaní {num} noví kamaráti\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Získali sme {num} handshake/-y/ov rúk\n"
msgid "Met 1 peer"
msgstr "Sretli sme 1 rovesníka"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Stretli sme {num} rovesníkov"
#, 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 "Pwnoval som {duration} a kickol som {deauthed} klienta/ov! Tiež som"
"stretol {associated} nového/ých kamaráta/ov a zjedol {handshakes} handshake/y!"
" #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "hodiny"
msgid "minutes"
msgstr "minúty"
msgid "seconds"
msgstr "sekundy"
msgid "hour"
msgstr "hodina"
msgid "minute"
msgstr "minúta"
msgid "second"
msgstr "sekunda"

Binary file not shown.

View File

@@ -0,0 +1,248 @@
# Interfaz en español para pwnagotchi
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# Angel Hernandez Segura, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-04 12:57+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Angel Hernandez Segura <ahsec.7@gmail.com>\n"
"Language-Team: Español <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hola, Soy Pwnagotchi! Iniciando..."
msgid "New day, new hunt, new pwns!"
msgstr "Un nuevo dia, nuevos objetivos, nuevos pwns"
msgid "Hack the Planet!"
msgstr "Hack the Planet!"
msgid "AI ready."
msgstr "IA lista"
msgid "The neural network is ready."
msgstr "La red neuronal esta lista"
msgid "Generating keys, do not turn off ..."
msgstr "Generando llaves, no apagar"
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, canal {channel} esta libre! Tu AP te lo agredecera"
msgid "Reading last session logs ..."
msgstr "Leyendo logs de la ultima sesion"
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "He leido {lines_so_far} lineas de los logs hasta ahora "
msgid "I'm bored ..."
msgstr "Estoy aburrido"
msgid "Let's go for a walk!"
msgstr "Vamos a caminar!"
msgid "This is the best day of my life!"
msgstr "Este es el mejor dia de mi vida"
msgid "Shitty day :/"
msgstr "Dia de mierda :/"
msgid "I'm extremely bored ..."
msgstr "Estoy extremadamente aburrido ..."
msgid "I'm very sad ..."
msgstr "Estoy mut triste"
msgid "I'm sad"
msgstr "Estoy triste"
msgid "Leave me alone ..."
msgstr "Dejame solo ..."
msgid "I'm mad at you!"
msgstr "Estoy enojado contigo!"
msgid "I'm living the life!"
msgstr "Estoy disfrutando la vida!"
msgid "I pwn therefore I am."
msgstr "Yo pwn, por lo tanto existo"
msgid "So many networks!!!"
msgstr "Tantas redes!!!"
msgid "I'm having so much fun!"
msgstr "Me estoy divirtiendo mucho!"
msgid "My crime is that of curiosity ..."
msgstr "Mi crimen es la curiosidad ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hola {name}! Mucho gusto."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Yo {name}! Que hay?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hey {name} como te va?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Unit {name} esta cerca!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... adios {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} se fue ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ... {name} se fue"
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} se ha perdido!"
msgid "Missed!"
msgstr "Perdido!"
msgid "Good friends are a blessing!"
msgstr "Los buenos amigos son una bendicion"
msgid "I love my friends!"
msgstr "Amo a mis amigos!"
msgid "Nobody wants to play with me ..."
msgstr "Nadie quiere jugar conmigo ..."
msgid "I feel so alone ..."
msgstr "Me siento muy solo ..."
msgid "Where's everybody?!"
msgstr "Donde estan todos?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Tomando una siesta por {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s) "
msgid "Good night."
msgstr "Buenas noches."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Esperando por {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Mirando alrededor ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what} vamos a ser amigos!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Asociandose a {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Yo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Acabo de decidir que {mac} no necesita WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "De-autenticando {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Vetando {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Bien, obtuvimos {num} nuevos handshake{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Tienes {count} nuevos mensajes{plural}!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salio mal ... Reiniciando ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Bloquee {num} staciones\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Hice {num} nuevos amigos\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Obtuve {num} handshakes\n"
msgid "Met 1 peer"
msgstr "Conoci a 1 unidad"
#, python-brace-format
msgid "Met {num} peers"
msgstr "conoci {num} unidades"
#, 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 "He estado hackeando por {duration} y de-autenticando {deauthed} "
"clientes! Tambien conoci {associated} nuevos amigos y comi {handshakes} "
"handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "horas"
msgid "minutes"
msgstr "minutos"
msgid "seconds"
msgstr "segundos"
msgid "hour"
msgstr "hora"
msgid "minute"
msgstr "minuto"
msgid "second"
msgstr "segundo"

View File

@@ -177,7 +177,7 @@ msgstr "Отакої, у нас є {num} нових рукостискань!"
msgid "You have {count} new message{plural}!"
msgstr "Нових повідомлень: {count}"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ой, щось пішло не так ... Перезавантажуюсь ..."
#, python-brace-format

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-04 12:57+0100\n"
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -198,7 +198,7 @@ msgstr ""
msgid "You have {count} new message{plural}!"
msgstr ""
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr ""
#, python-brace-format

View File

@@ -3,6 +3,9 @@ import time
import re
import os
import logging
import shutil
import gzip
import warnings
from datetime import datetime
from pwnagotchi.voice import Voice
@@ -26,7 +29,7 @@ class LastSession(object):
def __init__(self, config):
self.config = config
self.voice = Voice(lang=config['main']['lang'])
self.path = config['main']['log']
self.path = config['main']['log']['path']
self.last_session = []
self.last_session_id = ''
self.last_saved_session_id = ''
@@ -209,3 +212,98 @@ class LastSession(object):
def is_new(self):
return self.last_session_id != self.last_saved_session_id
def setup_logging(args, config):
cfg = config['main']['log']
filename = cfg['path']
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
root = logging.getLogger()
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
if filename:
# since python default log rotation might break session data in different files,
# we need to do log rotation ourselves
log_rotation(filename, cfg)
file_handler = logging.FileHandler(filename)
file_handler.setFormatter(formatter)
root.addHandler(file_handler)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
if not args.debug:
# disable scapy and tensorflow logging
logging.getLogger("scapy").disabled = True
logging.getLogger('tensorflow').disabled = True
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=DeprecationWarning)
# 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.prpagate = False
def log_rotation(filename, cfg):
rotation = cfg['rotation']
if not rotation['enabled']:
return
elif not os.path.isfile(filename):
return
stats = os.stat(filename)
# specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
if rotation['size']:
max_size = parse_max_size(rotation['size'])
if stats.st_size >= max_size:
do_rotate(filename, stats, cfg)
else:
raise Exception("log rotation is enabled but log.rotation.size was not specified")
def parse_max_size(s):
parts = re.findall(r'(^\d+)([bBkKmMgG]?)', s)
if len(parts) != 1 or len(parts[0]) != 2:
raise Exception("can't parse %s as a max size" % s)
num, unit = parts[0]
num = int(num)
unit = unit.lower()
if unit == 'k':
return num * 1024
elif unit == 'm':
return num * 1024 * 1024
elif unit == 'g':
return num * 1024 * 1024 * 1024
else:
return num
def do_rotate(filename, stats, cfg):
base_path = os.path.dirname(filename)
name = os.path.splitext(os.path.basename(filename))[0]
archive_filename = os.path.join(base_path, "%s.gz" % name)
counter = 2
while os.path.exists(archive_filename):
archive_filename = os.path.join(base_path, "%s-%d.gz" % (name, counter))
counter += 1
log_filename = archive_filename.replace('gz', 'log')
print("%s is %d bytes big, rotating to %s ..." % (filename, stats.st_size, log_filename))
shutil.move(filename, log_filename)
print("compressing to %s ..." % archive_filename)
with open(log_filename, 'rb') as src:
with gzip.open(archive_filename, 'wb') as dst:
dst.writelines(src)

View File

@@ -17,7 +17,7 @@ class AsyncAdvertiser(object):
self._keypair = keypair
self._advertisement = {
'name': pwnagotchi.name(),
'version': pwnagotchi.version,
'version': pwnagotchi.__version__,
'identity': self._keypair.fingerprint,
'face': faces.FRIEND,
'pwnd_run': 0,

View File

@@ -1,4 +1,5 @@
NumChannels = 140
NumChannelsExt = 165 # see https://github.com/evilsocket/pwnagotchi/issues/583
def freq_to_channel(freq):

View File

@@ -1,37 +1,98 @@
import os
import glob
import _thread
import threading
import importlib, importlib.util
import logging
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {}
database = {}
locks = {}
class Plugin:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
global loaded
global loaded, locks
plugin_name = cls.__module__.split('.')[0]
plugin_instance = cls()
logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance))
loaded[plugin_name] = plugin_instance
for attr_name in plugin_instance.__dir__():
if attr_name.startswith('on_'):
cb = getattr(plugin_instance, attr_name, None)
if cb is not None and callable(cb):
locks["%s::%s" % (plugin_name, attr_name)] = threading.Lock()
def toggle_plugin(name, enable=True):
"""
Load or unload a plugin
returns True if changed, otherwise False
"""
import pwnagotchi
from pwnagotchi.ui import view
from pwnagotchi.utils import save_config
global loaded, database
if pwnagotchi.config:
pwnagotchi.config['main']['plugins'][name]['enabled'] = enable
save_config(pwnagotchi.config, '/etc/pwnagotchi/config.toml')
if not enable and name in loaded:
if getattr(loaded[name], 'on_unload', None):
loaded[name].on_unload(view.ROOT)
del loaded[name]
return True
if enable and name in database and name not in loaded:
load_from_file(database[name])
one(name, 'loaded')
if pwnagotchi.config:
one(name, 'config_changed', pwnagotchi.config)
one(name, 'ui_setup', view.ROOT)
one(name, 'ready', view.ROOT._agent)
return True
return False
def on(event_name, *args, **kwargs):
for plugin_name, plugin in loaded.items():
for plugin_name in loaded.keys():
one(plugin_name, event_name, *args, **kwargs)
def locked_cb(lock_name, cb, *args, **kwargs):
global locks
if lock_name not in locks:
locks[lock_name] = threading.Lock()
with locks[lock_name]:
cb(*args, *kwargs)
def one(plugin_name, event_name, *args, **kwargs):
global loaded
if plugin_name in loaded:
plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name
callback = getattr(plugin, cb_name, None)
if callback is not None and callable(callback):
try:
callback(*args, **kwargs)
lock_name = "%s::%s" % (plugin_name, cb_name)
locked_cb_args = (lock_name, callback, *args, *kwargs)
_thread.start_new_thread(locked_cb, locked_cb_args)
except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True)
@@ -47,10 +108,11 @@ def load_from_file(filename):
def load_from_path(path, enabled=()):
global loaded
global loaded, database
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
database[plugin_name] = filename
if plugin_name in enabled:
try:
load_from_file(filename)
@@ -78,3 +140,4 @@ def load(config):
plugin.options = config['main']['plugins'][name]
on('loaded')
on('config_changed', config)

389
pwnagotchi/plugins/cmd.py Normal file
View File

@@ -0,0 +1,389 @@
# Handles the commandline stuff
import sys
import os
import logging
import glob
import re
import shutil
from fnmatch import fnmatch
from pwnagotchi.utils import download_file, unzip, save_config, parse_version, md5
from pwnagotchi.plugins import default_path
REPO_URL = 'https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip'
SAVE_DIR = '/usr/local/share/pwnagotchi/availaible-plugins/'
DEFAULT_INSTALL_PATH = '/usr/local/share/pwnagotchi/installed-plugins/'
def add_parsers(parser):
"""
Adds the plugins subcommand to a given argparse.ArgumentParser
"""
subparsers = parser.add_subparsers()
## pwnagotchi plugins
parser_plugins = subparsers.add_parser('plugins')
plugin_subparsers = parser_plugins.add_subparsers(dest='plugincmd')
## pwnagotchi plugins search
parser_plugins_search = plugin_subparsers.add_parser('search', help='Search for pwnagotchi plugins')
parser_plugins_search.add_argument('pattern', type=str, help="Search expression (wildcards allowed)")
## pwnagotchi plugins list
parser_plugins_list = plugin_subparsers.add_parser('list', help='List available pwnagotchi plugins')
parser_plugins_list.add_argument('-i', '--installed', action='store_true', required=False, help='List also installed plugins')
## pwnagotchi plugins update
parser_plugins_update = plugin_subparsers.add_parser('update', help='Updates the database')
## pwnagotchi plugins upgrade
parser_plugins_upgrade = plugin_subparsers.add_parser('upgrade', help='Upgrades plugins')
parser_plugins_upgrade.add_argument('pattern', type=str, nargs='?', default='*', help="Filter expression (wildcards allowed)")
## pwnagotchi plugins enable
parser_plugins_enable = plugin_subparsers.add_parser('enable', help='Enables a plugin')
parser_plugins_enable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins disable
parser_plugins_disable = plugin_subparsers.add_parser('disable', help='Disables a plugin')
parser_plugins_disable.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins install
parser_plugins_install = plugin_subparsers.add_parser('install', help='Installs a plugin')
parser_plugins_install.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins uninstall
parser_plugins_uninstall = plugin_subparsers.add_parser('uninstall', help='Uninstalls a plugin')
parser_plugins_uninstall.add_argument('name', type=str, help='Name of the plugin')
## pwnagotchi plugins edit
parser_plugins_edit = plugin_subparsers.add_parser('edit', help='Edit the options')
parser_plugins_edit.add_argument('name', type=str, help='Name of the plugin')
return parser
def used_plugin_cmd(args):
"""
Checks if the plugins subcommand was used
"""
return hasattr(args, 'plugincmd')
def handle_cmd(args, config):
"""
Parses the arguments and does the thing the user wants
"""
if args.plugincmd == 'update':
return update()
elif args.plugincmd == 'search':
args.installed = True # also search in installed plugins
return list_plugins(args, config, args.pattern)
elif args.plugincmd == 'install':
return install(args, config)
elif args.plugincmd == 'uninstall':
return uninstall(args, config)
elif args.plugincmd == 'list':
return list_plugins(args, config)
elif args.plugincmd == 'enable':
return enable(args, config)
elif args.plugincmd == 'disable':
return disable(args, config)
elif args.plugincmd == 'upgrade':
return upgrade(args, config, args.pattern)
elif args.plugincmd == 'edit':
return edit(args, config)
raise NotImplementedError()
def edit(args, config):
"""
Edit the config of the plugin
"""
plugin = args.name
editor = os.environ.get('EDITOR', 'vim') # because vim is the best
if plugin not in config['main']['plugins']:
return 1
plugin_config = {'main': {'plugins': {plugin: config['main']['plugins'][plugin]}}}
import toml
from subprocess import call
from tempfile import NamedTemporaryFile
from pwnagotchi.utils import DottedTomlEncoder
new_plugin_config = None
with NamedTemporaryFile(suffix=".tmp", mode='r+t') as tmp:
tmp.write(toml.dumps(plugin_config, encoder=DottedTomlEncoder()))
tmp.flush()
rc = call([editor, tmp.name])
if rc != 0:
return rc
tmp.seek(0)
new_plugin_config = toml.load(tmp)
config['main']['plugins'][plugin] = new_plugin_config['main']['plugins'][plugin]
save_config(config, args.user_config)
return 0
def enable(args, config):
"""
Enables the given plugin and saves the config to disk
"""
if args.name not in config['main']['plugins']:
config['main']['plugins'][args.name] = dict()
config['main']['plugins'][args.name]['enabled'] = True
save_config(config, args.user_config)
return 0
def disable(args, config):
"""
Disables the given plugin and saves the config to disk
"""
if args.name not in config['main']['plugins']:
config['main']['plugins'][args.name] = dict()
config['main']['plugins'][args.name]['enabled'] = False
save_config(config, args.user_config)
return 0
def upgrade(args, config, pattern='*'):
"""
Upgrades the given plugin
"""
available = _get_available()
installed = _get_installed(config)
for plugin, filename in installed.items():
if not fnmatch(plugin, pattern) or plugin not in available:
continue
available_version = _extract_version(available[plugin])
installed_version = _extract_version(filename)
if installed_version and available_version:
if available_version <= installed_version:
continue
else:
continue
logging.info('Upgrade %s from %s to %s', plugin, '.'.join(installed_version), '.'.join(available_version))
shutil.copyfile(available[plugin], installed[plugin])
# maybe has config
for conf in glob.glob(available[plugin].replace('.py', '.y?ml')):
dst = os.path.join(os.path.dirname(installed[plugin]), os.path.basename(conf))
if os.path.exists(dst) and md5(dst) != md5(conf):
# backup
logging.info('Backing up config: %s', os.path.basename(conf))
shutil.move(dst, dst + '.bak')
shutil.copyfile(conf, dst)
return 0
def list_plugins(args, config, pattern='*'):
"""
Lists the available and installed plugins
"""
found = False
line = "|{name:^{width}}|{version:^9}|{enabled:^10}|{status:^15}|"
available = _get_available()
installed = _get_installed(config)
available_and_installed = set(list(available.keys()) + list(installed.keys()))
available_not_installed = set(available.keys()) - set(installed.keys())
max_len_list = available_and_installed if args.installed else available_not_installed
max_len = max(map(len, max_len_list))
header = line.format(name='Plugin', width=max_len, version='Version', enabled='Active', status='Status')
line_length = max(max_len, len('Plugin')) + len(header) - len('Plugin') - 12 # lol
print('-' * line_length)
print(header)
print('-' * line_length)
if args.installed:
# only installed (maybe update available?)
for plugin, filename in sorted(installed.items()):
if not fnmatch(plugin, pattern):
continue
found = True
installed_version = _extract_version(filename)
available_version = None
if plugin in available:
available_version = _extract_version(available[plugin])
status = "installed"
if installed_version and available_version:
if available_version > installed_version:
status = "installed (^)"
enabled = 'enabled' if plugin in config['main']['plugins'] and \
'enabled' in config['main']['plugins'][plugin] and \
config['main']['plugins'][plugin]['enabled'] \
else 'disabled'
print(line.format(name=plugin, width=max_len, version='.'.join(installed_version), enabled=enabled, status=status))
for plugin in sorted(available_not_installed):
if not fnmatch(plugin, pattern):
continue
found = True
available_version = _extract_version(available[plugin])
print(line.format(name=plugin, width=max_len, version='.'.join(available_version), enabled='-', status='available'))
print('-' * line_length)
if not found:
logging.info('Maybe try: pwnagotchi plugins update')
return 1
return 0
def _extract_version(filename):
"""
Extracts the version from a python file
"""
plugin_content = open(filename, 'rt').read()
m = re.search(r'__version__[\t ]*=[\t ]*[\'\"]([^\"\']+)', plugin_content)
if m:
return parse_version(m.groups()[0])
return None
def _get_available():
"""
Get all availaible plugins
"""
available = dict()
for filename in glob.glob(os.path.join(SAVE_DIR, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
available[plugin_name] = filename
return available
def _get_installed(config):
"""
Get all installed plugins
"""
installed = dict()
search_dirs = [ default_path, config['main']['custom_plugins'] ]
for search_dir in search_dirs:
if search_dir:
for filename in glob.glob(os.path.join(search_dir, "*.py")):
plugin_name = os.path.basename(filename.replace(".py", ""))
installed[plugin_name] = filename
return installed
def uninstall(args, config):
"""
Uninstalls a plugin
"""
plugin_name = args.name
installed = _get_installed(config)
if plugin_name not in installed:
logging.error('Plugin %s is not installed.', plugin_name)
return 1
os.remove(installed[plugin_name])
return 0
def install(args, config):
"""
Installs the given plugin
"""
global DEFAULT_INSTALL_PATH
plugin_name = args.name
available = _get_available()
installed = _get_installed(config)
if plugin_name not in available:
logging.error('%s not found.', plugin_name)
return 1
if plugin_name in installed:
logging.error('%s already installed.', plugin_name)
# install into custom_plugins path
install_path = config['main']['custom_plugins']
if not install_path:
install_path = DEFAULT_INSTALL_PATH
config['main']['custom_plugins'] = install_path
save_config(config, args.user_config)
os.makedirs(install_path, exist_ok=True)
shutil.copyfile(available[plugin_name], os.path.join(install_path, os.path.basename(available[plugin_name])))
# maybe has config
for conf in glob.glob(available[plugin_name].replace('.py', '.y?ml')):
dst = os.path.join(install_path, os.path.basename(conf))
if os.path.exists(dst) and md5(dst) != md5(conf):
# backup
logging.info('Backing up config: %s', os.path.basename(conf))
shutil.move(dst, dst + '.bak')
shutil.copyfile(conf, dst)
return 0
def _analyse_dir(path):
results = dict()
path += '*' if path.endswith('/') else '/*'
for filename in glob.glob(path, recursive=True):
if not os.path.isfile(filename):
continue
try:
results[filename] = md5(filename)
except OSError:
continue
return results
def update():
"""
Updates the database
"""
global REPO_URL, SAVE_DIR
DEST = os.path.join(SAVE_DIR, 'plugins.zip')
logging.info('Downloading plugins to %s', DEST)
try:
os.makedirs(SAVE_DIR, exist_ok=True)
before_update = _analyse_dir(SAVE_DIR)
download_file(REPO_URL, os.path.join(SAVE_DIR, DEST))
logging.info('Unzipping...')
unzip(DEST, SAVE_DIR, strip_dirs=1)
after_update = _analyse_dir(SAVE_DIR)
b_len = len(before_update)
a_len = len(after_update)
if a_len > b_len:
logging.info('Found %d new file(s).', a_len - b_len)
changed = 0
for filename, filehash in after_update.items():
if filename in before_update and filehash != before_update[filename]:
changed += 1
if changed:
logging.info('%d file(s) were changed.', changed)
return 0
except Exception as ex:
logging.error('Error while updating plugins %s', ex)
return 1

View File

@@ -1,57 +0,0 @@
import pwnagotchi.plugins as plugins
import logging
import subprocess
import string
import os
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
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'
def __init__(self):
super().__init__(self)
self.text_to_set = ""
def on_loaded(self):
logging.info("aircrackonly plugin loaded")
def on_handshake(self, agent, filename, access_point, client_station):
display = agent._view
todelete = 0
handshakeFound = 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:
handshakeFound = 1
logging.info("[AircrackOnly] contains handshake")
if handshakeFound == 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
if todelete == 1:
os.remove(filename)
self.text_to_set = "Removed an uncrackable pcap"
display.update(force=True)
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 = ""

View File

@@ -1,65 +0,0 @@
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
import logging
import os
import subprocess
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 __init__(self):
self.ready = False
self.tries = 0
self.status = StatusFile('/root/.auto-backup')
def on_loaded(self):
for opt in ['files', 'interval', 'commands', 'max_tries']:
if opt not in self.options or (opt in self.options and self.options[opt] is None):
logging.error(f"AUTO-BACKUP: Option {opt} is not set.")
return
self.ready = True
logging.info("AUTO-BACKUP: Successfully loaded.")
def on_internet_available(self, agent):
if not self.ready:
return
if self.options['max_tries'] and self.tries >= self.options['max_tries']:
return
if self.status.newer_then_days(self.options['interval']):
return
# Only backup existing files to prevent errors
existing_files = list(filter(lambda f: os.path.exists(f), self.options['files']))
files_to_backup = " ".join(existing_files)
try:
display = agent.view()
logging.info("AUTO-BACKUP: Backing up ...")
display.set('status', 'Backing up ...')
display.update()
for cmd in self.options['commands']:
logging.info(f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}")
process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
raise OSError(f"Command failed (rc: {process.returncode})")
logging.info("AUTO-BACKUP: backup done")
display.set('status', 'Backup done!')
display.update()
self.status.update()
except OSError as os_e:
self.tries += 1
logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup failed!')
display.update()

View File

@@ -6,11 +6,11 @@ import requests
import platform
import shutil
import glob
import pkg_resources
from threading import Lock
import pwnagotchi
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
from pwnagotchi.utils import StatusFile, parse_version as version_to_tuple
def check(version, repo, native=True):
@@ -29,8 +29,8 @@ def check(version, repo, native=True):
info['available'] = latest_ver = latest['tag_name'].replace('v', '')
is_arm = info['arch'].startswith('arm')
local = pkg_resources.parse_version(info['current'])
remote = pkg_resources.parse_version(latest_ver)
local = version_to_tuple(info['current'])
remote = version_to_tuple(latest_ver)
if remote > local:
if not native:
info['url'] = "https://github.com/%s/archive/%s.zip" % (repo, latest['tag_name'])
@@ -150,68 +150,74 @@ class AutoUpdate(plugins.Plugin):
def __init__(self):
self.ready = False
self.status = StatusFile('/root/.auto-update')
self.lock = Lock()
def on_loaded(self):
if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
if 'interval' not in self.options or ('interval' in self.options and not self.options['interval']):
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:
if self.lock.locked():
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
with self.lock:
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)
logging.info("[update] checking for updates ...")
if not self.ready:
return
display = agent.view()
prev_status = display.get('status')
if self.status.newer_then_hours(self.options['interval']):
logging.debug("[update] last check happened less than %d hours ago" % self.options['interval'])
return
try:
display.update(force=True, new_data={'status': 'Checking for updates ...'})
logging.info("[update] checking for updates ...")
to_install = []
to_check = [
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
]
display = agent.view()
prev_status = display.get('status')
for repo, local_version, is_native, svc_name in to_check:
info = check(local_version, repo, is_native)
if info['url'] is not None:
logging.warning(
"update for %s available (local version is '%s'): %s" % (
repo, info['current'], info['url']))
info['service'] = svc_name
to_install.append(info)
try:
display.update(force=True, new_data={'status': 'Checking for updates ...'})
num_updates = len(to_install)
num_installed = 0
to_install = []
to_check = [
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
('evilsocket/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
]
if num_updates > 0:
if self.options['install']:
for update in to_install:
if install(display, update):
num_installed += 1
else:
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
for repo, local_version, is_native, svc_name in to_check:
info = check(local_version, repo, is_native)
if info['url'] is not None:
logging.warning(
"update for %s available (local version is '%s'): %s" % (
repo, info['current'], info['url']))
info['service'] = svc_name
to_install.append(info)
logging.info("[update] done")
num_updates = len(to_install)
num_installed = 0
self.status.update()
if num_updates > 0:
if self.options['install']:
for update in to_install:
plugins.on('updating')
if install(display, update):
num_installed += 1
else:
prev_status = '%d new update%c available!' % (num_updates, 's' if num_updates > 1 else '')
if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'})
pwnagotchi.reboot()
logging.info("[update] done")
except Exception as e:
logging.error("[update] %s" % e)
self.status.update()
display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})
if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'})
pwnagotchi.reboot()
except Exception as e:
logging.error("[update] %s" % e)
display.update(force=True, new_data={'status': prev_status if prev_status is not None else ''})

View File

@@ -1,14 +1,16 @@
import os
import time
import re
import logging
import os
import subprocess
import time
from threading import Lock
import dbus
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts
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
class BTError(Exception):
@@ -382,7 +384,7 @@ class IfaceWrapper:
class Device:
def __init__(self, name, share_internet, mac, ip, netmask, interval, priority=10, scantime=15, search_order=0, max_tries=0, **kwargs):
def __init__(self, name, share_internet, mac, ip, netmask, interval, gateway=None, 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()
@@ -394,6 +396,7 @@ class Device:
self.share_internet = share_internet
self.ip = ip
self.netmask = netmask
self.gateway = gateway
self.interval = interval
self.mac = mac
self.scantime = scantime
@@ -424,6 +427,7 @@ class BTTether(plugins.Plugin):
self.ready = False
self.options = dict()
self.devices = dict()
self.lock = Lock()
def on_loaded(self):
# new config
@@ -433,7 +437,7 @@ class BTTether(plugins.Plugin):
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):
if device_opt not in options or options[device_opt] is None:
logging.error("BT-TETHER: Please specify the %s for device %s.",
device_opt, device)
break
@@ -444,7 +448,7 @@ class BTTether(plugins.Plugin):
# 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):
if opt not in self.options or self.options[opt] is None:
logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
return
@@ -461,110 +465,119 @@ class BTTether(plugins.Plugin):
logging.error("BT-TETHER: Can't start bluetooth.service")
return
logging.info("BT-TETHER: Sussessfully loaded ...")
logging.info("BT-TETHER: Successfully loaded ...")
self.ready = True
def on_unload(self, ui):
with ui._lock:
ui.remove_element('bluetooth')
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))
with ui._lock:
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
devices_to_try = list()
connected_priorities = list()
any_device_connected = False # if this is true, last status on screen should be C
with self.lock:
devices_to_try = list()
connected_priorities = list()
any_device_connected = False # if this is true, last status on screen should be C
for _, device in self.devices.items():
if device.connected():
connected_priorities.append(device.priority)
any_device_connected = True
continue
for _, device in self.devices.items():
if device.connected():
connected_priorities.append(device.priority)
any_device_connected = True
continue
if not device.max_tries or (device.max_tries > device.tries):
if not device.status.newer_then_minutes(device.interval):
devices_to_try.append(device)
device.status.update()
device.tries += 1
if not device.max_tries or (device.max_tries > device.tries):
if not device.status.newer_then_minutes(device.interval):
devices_to_try.append(device)
device.status.update()
device.tries += 1
sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
for device in sorted_devices:
bt = BTNap(device.mac)
for device in sorted_devices:
bt = BTNap(device.mac)
try:
logging.debug('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.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
try:
logging.debug('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.debug('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')
continue
except Exception as bt_ex:
logging.error(bt_ex)
ui.set('bluetooth', 'NF')
continue
paired = bt.is_paired()
if not paired:
if BTNap.pair(dev_remote):
logging.debug('BT-TETHER: Paired with %s.', device.name)
paired = bt.is_paired()
if not paired:
if BTNap.pair(dev_remote):
logging.debug('BT-TETHER: Paired with %s.', device.name)
else:
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
ui.set('bluetooth', 'PE')
continue
else:
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
ui.set('bluetooth', 'PE')
continue
else:
logging.debug('BT-TETHER: Already paired.')
logging.debug('BT-TETHER: Already paired.')
logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name)
device.network, success = BTNap.nap(dev_remote)
interface = None
logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name)
device.network, success = BTNap.nap(dev_remote)
interface = None
if success:
try:
interface = device.interface()
except Exception:
if success:
try:
interface = device.interface()
except Exception:
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
continue
if interface is None:
ui.set('bluetooth', 'BE')
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
continue
logging.debug('BT-TETHER: Created interface (%s)', interface)
ui.set('bluetooth', 'C')
any_device_connected = True
device.tries = 0 # reset tries
else:
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
ui.set('bluetooth', 'NF')
continue
if interface is None:
ui.set('bluetooth', 'BE')
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
addr = f"{device.ip}/{device.netmask}"
if device.gateway:
gateway = device.gateway
else:
gateway = ".".join(device.ip.split('.')[:-1] + ['1'])
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.debug("BT-TETHER: Could not add ip to %s", interface)
continue
logging.debug('BT-TETHER: Created interface (%s)', 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)
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.debug('BT-TETHER: Added nameserver')
resolv.seek(0)
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if any_device_connected:
ui.set('bluetooth', 'C')
any_device_connected = True
device.tries = 0 # reset tries
else:
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
ui.set('bluetooth', 'NF')
continue
addr = f"{device.ip}/{device.netmask}"
gateway = ".".join(device.ip.split('.')[:-1] + ['1'])
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.debug("BT-TETHER: Could not add ip to %s", interface)
continue
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)
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.debug('BT-TETHER: Added nameserver')
resolv.seek(0)
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if any_device_connected:
ui.set('bluetooth', 'C')

View File

@@ -16,27 +16,20 @@ class Example(plugins.Plugin):
logging.debug("example plugin created")
# called when http://<host>:<port>/plugins/<plugin>/ is called
# must return a response
def on_webhook(self, path, args, req_method):
# must return a html page
# IMPORTANT: If you use "POST"s, add a csrf-token (via csrf_token() and render_template_string)
def on_webhook(self, path, request):
pass
# called when the plugin is loaded
def on_loaded(self):
logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
# called when <host>:<port>/plugins/<pluginname> is opened
def on_webhook(self, response, path):
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
# called before the plugin is unloaded
def on_unload(self, ui):
pass
try:
response.wfile.write(bytes(res, "utf-8"))
except Exception as ex:
logging.error(ex)
# called in manual mode when there's internet connectivity
# called hen there's internet connectivity
def on_internet_available(self, agent):
pass
@@ -106,7 +99,7 @@ class Example(plugins.Plugin):
pass
# called when the status is set to excited
def on_excited(aself, gent):
def on_excited(self, agent):
pass
# called when the status is set to lonely
@@ -129,6 +122,11 @@ class Example(plugins.Plugin):
def on_wifi_update(self, agent, access_points):
pass
# called when the agent refreshed an unfiltered access point list
# this list contains all access points that were detected BEFORE filtering
def on_unfiltered_ap_list(self, agent, access_points):
pass
# called when the agent is sending an association frame
def on_association(self, agent, access_point):
pass

View File

@@ -31,10 +31,9 @@ class GPIOButtons(plugins.Plugin):
# set gpio numbering
GPIO.setmode(GPIO.BCM)
for i in gpios:
gpio = list(i)[0]
command = i[gpio]
for gpio, command in gpios.items():
gpio = int(gpio)
self.ports[gpio] = command
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=600)
logging.info("Added command: %s to GPIO #%d", command, gpio)

View File

@@ -1,42 +1,137 @@
import logging
import json
import logging
import os
import pwnagotchi.plugins as plugins
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
class GPS(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
__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
self.coordinates = None
def on_loaded(self):
logging.info("gps plugin loaded for %s" % self.options['device'])
logging.info(f"gps plugin loaded for {self.options['device']}")
def on_ready(self, agent):
if os.path.exists(self.options['device']):
logging.info("enabling gps bettercap's module for %s" % self.options['device'])
if os.path.exists(self.options["device"]):
logging.info(
f"enabling bettercap's gps module for {self.options['device']}"
)
try:
agent.run('gps off')
except:
agent.run("gps off")
except Exception:
pass
agent.run('set gps.device %s' % self.options['device'])
agent.run('set gps.speed %d' % self.options['speed'])
agent.run('gps on')
running = True
agent.run(f"set gps.device {self.options['device']}")
agent.run(f"set gps.baudrate {self.options['speed']}")
agent.run("gps on")
self.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')
self.coordinates = 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)
if self.coordinates and all([
# avoid 0.000... measurements
self.coordinates["Latitude"], self.coordinates["Longitude"]
]):
logging.info(f"saving GPS to {gps_filename} ({self.coordinates})")
with open(gps_filename, "w+t") as fp:
json.dump(self.coordinates, fp)
else:
logging.info("not saving GPS. Couldn't find location.")
def on_ui_setup(self, ui):
# add coordinates for other displays
if ui.is_waveshare_v2():
lat_pos = (127, 75)
lon_pos = (122, 84)
alt_pos = (127, 94)
elif ui.is_waveshare_v1():
lat_pos = (130, 70)
lon_pos = (125, 80)
alt_pos = (130, 90)
elif ui.is_inky():
lat_pos = (127, 60)
lon_pos = (127, 70)
alt_pos = (127, 80)
elif ui.is_waveshare144lcd():
# guessed values, add tested ones if you can
lat_pos = (67, 73)
lon_pos = (62, 83)
alt_pos = (67, 93)
else:
# guessed values, add tested ones if you can
lat_pos = (127, 51)
lon_pos = (127, 56)
alt_pos = (102, 71)
label_spacing = 0
ui.add_element(
"latitude",
LabeledValue(
color=BLACK,
label="lat:",
value="-",
position=lat_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
),
)
ui.add_element(
"longitude",
LabeledValue(
color=BLACK,
label="long:",
value="-",
position=lon_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
),
)
ui.add_element(
"altitude",
LabeledValue(
color=BLACK,
label="alt:",
value="-",
position=alt_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
),
)
def on_unload(self, ui):
with ui._lock:
ui.remove_element('latitude')
ui.remove_element('longitude')
ui.remove_element('altitude')
def on_ui_update(self, ui):
if self.coordinates and all([
# avoid 0.000... measurements
self.coordinates["Latitude"], self.coordinates["Longitude"]
]):
# last char is sometimes not completely drawn ¯\_(ツ)_/¯
# using an ending-whitespace as workaround on each line
ui.set("latitude", f"{self.coordinates['Latitude']:.4f} ")
ui.set("longitude", f" {self.coordinates['Longitude']:.4f} ")
ui.set("altitude", f" {self.coordinates['Altitude']:.1f}m ")

View File

@@ -7,6 +7,7 @@ import re
import pwnagotchi.grid as grid
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
from threading import Lock
def parse_pcap(filename):
@@ -54,6 +55,7 @@ class Grid(plugins.Plugin):
self.unread_messages = 0
self.total_messages = 0
self.lock = Lock()
def is_excluded(self, what):
for skip in self.options['exclude']:
@@ -67,7 +69,8 @@ class Grid(plugins.Plugin):
logging.info("grid plugin loaded.")
def set_reported(self, reported, net_id):
reported.append(net_id)
if net_id not in reported:
reported.append(net_id)
self.report.update(data={'reported': reported})
def check_inbox(self, agent):
@@ -77,6 +80,7 @@ class Grid(plugins.Plugin):
self.unread_messages = len([m for m in messages if m['seen_at'] is None])
if self.unread_messages:
plugins.on('unread_inbox', self.unread_messages)
logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages))
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
@@ -120,21 +124,25 @@ class Grid(plugins.Plugin):
def on_internet_available(self, agent):
logging.debug("internet available")
try:
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
if self.lock.locked():
return
try:
self.check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
with self.lock:
try:
grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
return
try:
self.check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)
try:
self.check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try:
self.check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)

View File

@@ -0,0 +1,159 @@
from threading import Event
import _thread
import logging
import time
import pwnagotchi.plugins as plugins
class Led(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin blinks the PWR led with different patterns depending on the event.'
def __init__(self):
self._is_busy = False
self._event = Event()
self._event_name = None
self._led_file = "/sys/class/leds/led0/brightness"
self._delay = 200
# called when the plugin is loaded
def on_loaded(self):
self._led_file = "/sys/class/leds/led%d/brightness" % self.options['led']
self._delay = int(self.options['delay'])
logging.info("[led] plugin loaded for %s" % self._led_file)
self._on_event('loaded')
_thread.start_new_thread(self._worker, ())
def _on_event(self, event):
if not self._is_busy:
self._event_name = event
self._event.set()
logging.debug("[led] event '%s' set", event)
else:
logging.debug("[led] skipping event '%s' because the worker is busy", event)
def _led(self, on):
with open(self._led_file, 'wt') as fp:
fp.write(str(on))
def _blink(self, pattern):
logging.debug("[led] using pattern '%s' ..." % pattern)
for c in pattern:
if c == ' ':
self._led(1)
else:
self._led(0)
time.sleep(self._delay / 1000.0)
# reset
self._led(0)
def _worker(self):
while True:
self._event.wait()
self._event.clear()
self._is_busy = True
try:
if self._event_name in self.options['patterns']:
pattern = self.options['patterns'][self._event_name]
self._blink(pattern)
else:
logging.debug("[led] no pattern defined for %s" % self._event_name)
except Exception as e:
logging.exception("[led] error while blinking")
finally:
self._is_busy = False
# called when the unit is updating its software
def on_updating(self):
self._on_event('updating')
# called when there's one or more unread pwnmail messages
def on_unread_inbox(self, num_unread):
self._on_event('unread_inbox')
# called when there's internet connectivity
def on_internet_available(self, agent):
self._on_event('internet_available')
# called when everything is ready and the main loop is about to start
def on_ready(self, agent):
self._on_event('ready')
# called when the AI finished loading
def on_ai_ready(self, agent):
self._on_event('ai_ready')
# called when the AI starts training for a given number of epochs
def on_ai_training_start(self, agent, epochs):
self._on_event('ai_training_start')
# called when the AI got the best reward so far
def on_ai_best_reward(self, agent, reward):
self._on_event('ai_best_reward')
# called when the AI got the worst reward so far
def on_ai_worst_reward(self, agent, reward):
self._on_event('ai_worst_reward')
# called when the status is set to bored
def on_bored(self, agent):
self._on_event('bored')
# called when the status is set to sad
def on_sad(self, agent):
self._on_event('sad')
# called when the status is set to excited
def on_excited(self, agent):
self._on_event('excited')
# called when the status is set to lonely
def on_lonely(self, agent):
self._on_event('lonely')
# called when the agent is rebooting the board
def on_rebooting(self, agent):
self._on_event('rebooting')
# called when the agent is waiting for t seconds
def on_wait(self, agent, t):
self._on_event('wait')
# called when the agent is sleeping for t seconds
def on_sleep(self, agent, t):
self._on_event('sleep')
# called when the agent refreshed its access points list
def on_wifi_update(self, agent, access_points):
self._on_event('wifi_update')
# called when the agent is sending an association frame
def on_association(self, agent, access_point):
self._on_event('association')
# called when the agent is deauthenticating a client station from an AP
def on_deauthentication(self, agent, access_point, client_station):
self._on_event('deauthentication')
# 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):
self._on_event('handshake')
# 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):
self._on_event('epoch')
# called when a new peer is detected
def on_peer_detected(self, agent, peer):
self._on_event('peer_detected')
# called when a known peer is lost
def on_peer_lost(self, agent, peer):
self._on_event('peer_lost')

View File

@@ -0,0 +1,282 @@
import os
import logging
import threading
from time import sleep
from datetime import datetime,timedelta
from pwnagotchi import plugins
from pwnagotchi.utils import StatusFile
from flask import render_template_string
from flask import jsonify
from flask import abort
from flask import Response
TEMPLATE = """
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Logtail
{% endblock %}
{% block styles %}
{{ super() }}
<style>
* {
box-sizing: border-box;
}
#filter {
width: 100%;
font-size: 16px;
padding: 12px 20px 12px 40px;
border: 1px solid #ddd;
margin-bottom: 12px;
}
table {
border-collapse: collapse;
width: 100%;
border: 1px solid #ddd;
}
th, td {
text-align: left;
padding: 12px;
width: 1px;
white-space: nowrap;
}
td:nth-child(2) {
text-align: center;
}
thead, tr:hover {
background-color: #f1f1f1;
}
tr {
border-bottom: 1px solid #ddd;
}
div.sticky {
position: -webkit-sticky;
position: sticky;
top: 0;
display: table;
width: 100%;
}
div.sticky > * {
display: table-cell;
}
div.sticky > span {
width: 1%;
}
div.sticky > input {
width: 100%;
}
tr.default {
color: black;
}
tr.info {
color: black;
}
tr.warning {
color: darkorange;
}
tr.error {
color: crimson;
}
tr.debug {
color: blueviolet;
}
.ui-mobile .ui-page-active {
overflow: visible;
overflow-x: visible;
}
</style>
{% endblock %}
{% block script %}
var content = document.getElementById('content');
var filter = document.getElementById('filter');
var filterVal = filter.value.toUpperCase();
var xhr = new XMLHttpRequest();
xhr.open('GET', '{{ url_for('plugins') }}/logtail/stream');
xhr.send();
var position = 0;
var data;
var time;
var level;
var msg;
var colorClass;
function handleNewData() {
var messages = xhr.responseText.split('\\n');
filterVal = filter.value.toUpperCase();
messages.slice(position, -1).forEach(function(value) {
if (value.charAt(0) != '[') {
msg = value;
time = '';
level = '';
} else {
data = value.split(']');
time = data.shift() + ']';
level = data.shift() + ']';
msg = data.join(']');
switch(level) {
case ' [INFO]':
colorClass = 'info';
break;
case ' [WARNING]':
colorClass = 'warning';
break;
case ' [ERROR]':
colorClass = 'error';
break;
case ' [DEBUG]':
colorClass = 'debug';
break;
default:
colorClass = 'default';
break;
}
}
var tr = document.createElement('tr');
var td1 = document.createElement('td');
var td2 = document.createElement('td');
var td3 = document.createElement('td');
td1.textContent = time;
td2.textContent = level;
td3.textContent = msg;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tr.className = colorClass;
if (filterVal.length > 0 && value.toUpperCase().indexOf(filterVal) == -1) {
tr.style.visibility = "collapse";
}
content.appendChild(tr);
});
position = messages.length - 1;
}
var scrollingElement = (document.scrollingElement || document.body)
function scrollToBottom () {
scrollingElement.scrollTop = scrollingElement.scrollHeight;
}
var timer;
var scrollElm = document.getElementById('autoscroll');
timer = setInterval(function() {
handleNewData();
if (scrollElm.checked) {
scrollToBottom();
}
if (xhr.readyState == XMLHttpRequest.DONE) {
clearInterval(timer);
}
}, 1000);
var typingTimer;
var doneTypingInterval = 1000;
filter.onkeyup = function() {
clearTimeout(typingTimer);
typingTimer = setTimeout(doneTyping, doneTypingInterval);
}
filter.onkeydown = function() {
clearTimeout(typingTimer);
}
function doneTyping() {
document.body.style.cursor = 'progress';
var table, tr, tds, td, i, txtValue;
filterVal = filter.value.toUpperCase();
table = document.getElementById("content");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
tds = tr[i].getElementsByTagName("td");
if (tds) {
for (l = 0; l < tds.length; l++) {
td = tds[l];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.visibility = "visible";
break;
} else {
tr[i].style.visibility = "collapse";
}
}
}
}
}
document.body.style.cursor = 'default';
}
{% endblock %}
{% block content %}
<div class="sticky">
<input type="text" id="filter" placeholder="Search for ..." title="Type in a filter">
<span><input checked type="checkbox" id="autoscroll"></span>
<span><label for="autoscroll"> Autoscroll to bottom</label><br></span>
</div>
<table id="content">
<thead>
<th>
Time
</th>
<th>
Level
</th>
<th>
Message
</th>
</thead>
</table>
{% endblock %}
"""
class Logtail(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin tails the logfile.'
def __init__(self):
self.lock = threading.Lock()
self.options = dict()
self.ready = False
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("Logtail plugin loaded.")
def on_webhook(self, path, request):
if not self.ready:
return "Plugin not ready"
if not path or path == "/":
return render_template_string(TEMPLATE)
if path == 'stream':
def generate():
with open(self.config['main']['log']['path']) as f:
yield f.read()
while True:
yield f.readline()
return Response(generate(), mimetype='text/plain')
abort(404)

View File

@@ -44,24 +44,52 @@ class MemTemp(plugins.Plugin):
if ui.is_waveshare_v2():
h_pos = (180, 80)
v_pos = (180, 61)
elif ui.is_waveshare_v1():
h_pos = (170, 80)
v_pos = (170, 61)
elif ui.is_waveshare144lcd():
h_pos = (53, 77)
v_pos = (78, 67)
elif ui.is_inky():
h_pos = (140, 68)
v_pos = (165, 54)
elif ui.is_waveshare27inch():
h_pos = (192, 138)
v_pos = (216, 122)
else:
h_pos = (155, 76)
v_pos = (180, 61)
if self.options['orientation'] == "horizontal":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=h_pos,
label_font=fonts.Small, text_font=fonts.Small))
elif self.options['orientation'] == "vertical":
if self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=v_pos,
label_font=fonts.Small, text_font=fonts.Small))
else:
# default to horizontal
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=h_pos,
label_font=fonts.Small, text_font=fonts.Small))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('memtemp')
def on_ui_update(self, ui):
if self.options['orientation'] == "horizontal":
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
if self.options['scale'] == "fahrenheit":
temp = (pwnagotchi.temperature() * 9 / 5) + 32
symbol = "f"
elif self.options['scale'] == "kelvin":
temp = pwnagotchi.temperature() + 273.15
symbol = "k"
else:
# default to celsius
temp = pwnagotchi.temperature()
symbol = "c"
elif self.options['orientation'] == "vertical":
if self.options['orientation'] == "vertical":
ui.set('memtemp',
" mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
" mem:%s%%\n cpu:%s%%\ntemp:%s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
else:
# default to horizontal
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))

View File

@@ -1,35 +1,39 @@
import logging
import json
import os
import threading
import requests
import time
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
class NetPos(plugins.Plugin):
__author__ = 'zenzen san'
__version__ = '2.0.1'
__version__ = '2.0.3'
__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 """
API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
def __init__(self):
self.report = StatusFile('/root/.net_pos_saved', data_format='json')
self.skip = list()
self.ready = False
self.lock = threading.Lock()
def on_loaded(self):
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
if 'api_key' not in self.options or ('api_key' in self.options and not self.options['api_key']):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
return
if 'api_url' in self.options:
self.API_URL = self.options['api_url']
self.ready = True
logging.info("net-pos plugin loaded.")
logging.debug(f"net-pos: use api_url: {self.API_URL}");
def _append_saved(self, path):
to_save = list()
@@ -45,60 +49,66 @@ class NetPos(plugins.Plugin):
saved_file.write(x + "\n")
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 self.lock.locked():
return
with self.lock:
if self.ready:
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
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(self.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):
if new_np_files:
logging.debug("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
display.update(force=True)
for idx, np_file in enumerate(new_np_files):
geo_file = np_file.replace('.net-pos.json', '.geo.json')
if os.path.exists(geo_file):
# got already the position
reported.append(np_file)
self.report.update(data={'reported': reported})
continue
try:
geo_data = self._get_geo_data(np_file) # returns json obj
except requests.exceptions.RequestException as req_e:
logging.error("NET-POS: %s - RequestException: %s", np_file, req_e)
self.skip += np_file
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s - JSONDecodeError: %s, removing it...", np_file, js_e)
os.remove(np_file)
continue
except OSError as os_e:
logging.error("NET-POS: %s - OSError: %s", np_file, os_e)
self.skip += np_file
continue
with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf)
geo_file = np_file.replace('.net-pos.json', '.geo.json')
if os.path.exists(geo_file):
# got already the position
reported.append(np_file)
self.report.update(data={'reported': reported})
continue
try:
geo_data = self._get_geo_data(np_file) # returns json obj
except requests.exceptions.RequestException as req_e:
logging.error("NET-POS: %s", 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
with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf)
reported.append(np_file)
self.report.update(data={'reported': reported})
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
display.update(force=True)
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
display.update(force=True)
def on_handshake(self, agent, filename, access_point, client_station):
netpos = self._get_netpos(agent)
if not netpos['wifiAccessPoints']:
return
netpos["ts"] = int("%.0f" % time.time())
netpos_filename = filename.replace('.pcap', '.net-pos.json')
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
logging.debug("NET-POS: Saving net-location to %s", netpos_filename)
try:
with open(netpos_filename, 'w+t') as net_pos_file:
@@ -106,6 +116,7 @@ class NetPos(plugins.Plugin):
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
def _get_netpos(self, agent):
aps = agent.get_access_points()
netpos = dict()
@@ -117,7 +128,7 @@ class NetPos(plugins.Plugin):
return netpos
def _get_geo_data(self, path, timeout=30):
geourl = MOZILLA_API_URL.format(api=self.options['api_key'])
geourl = self.API_URL.format(api=self.options['api_key'])
try:
with open(path, "r") as json_file:

View File

@@ -1,49 +1,45 @@
import os
import csv
import logging
import re
import requests
from pwnagotchi.utils import StatusFile
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile, remove_whitelisted
import pwnagotchi.plugins as plugins
from json.decoder import JSONDecodeError
class OnlineHashCrack(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__version__ = '2.0.1'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
def __init__(self):
self.ready = False
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
try:
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
except JSONDecodeError:
os.remove('/root/.ohc_uploads')
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
self.lock = Lock()
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
if 'email' not in self.options or ('email' in self.options and self.options['email'] is None):
if 'email' not in self.options or ('email' in self.options and not self.options['email']):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
return
if 'whitelist' not in self.options:
self.options['whitelist'] = []
# remove special characters from whitelist APs to match on-disk format
self.options['whitelist'] = set(map(lambda x: re.sub(r'[^a-zA-Z0-9]', '', x), self.options['whitelist']))
self.options['whitelist'] = list()
self.ready = True
logging.info("OHC: OnlineHashCrack plugin loaded.")
def _filter_handshake_file(self, handshake_filename):
try:
basename = os.path.basename(handshake_filename)
ssid, bssid = basename.split('_')
# remove the ".pcap" from the bssid (which is really just the end of the filename)
bssid = bssid[:-5]
except:
# something failed in our parsing of the filename. let the file through
return True
return ssid not in self.options['whitelist'] and bssid not in self.options['whitelist']
def _upload_to_ohc(self, path, timeout=30):
"""
@@ -64,37 +60,55 @@ class OnlineHashCrack(plugins.Plugin):
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
raise e
def _download_cracked(self, save_file, timeout=120):
"""
Downloads the cracked passwords and saves them
returns the number of downloaded passwords
"""
try:
s = requests.Session()
dashboard = s.get(self.options['dashboard'], timeout=timeout)
result = s.get('https://www.onlinehashcrack.com/wpa-exportcsv', timeout=timeout)
result.raise_for_status()
with open(save_file, 'wb') as output_file:
output_file.write(result.content)
except requests.exceptions.RequestException as req_e:
raise req_e
except OSError as os_e:
raise os_e
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
"""
if self.ready:
if not self.ready or self.lock.locked():
return
with self.lock:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
# pull out whitelisted APs
handshake_paths = filter(lambda path: self._filter_handshake_file(path), handshake_paths)
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._upload_to_ohc(handshake)
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
@@ -103,3 +117,24 @@ class OnlineHashCrack(plugins.Plugin):
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
continue
if 'dashboard' in self.options and self.options['dashboard']:
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
try:
self._download_cracked(cracked_file)
logging.info("OHC: Downloaded cracked passwords.")
except requests.exceptions.RequestException as req_e:
logging.debug("OHC: %s", req_e)
except OSError as os_e:
logging.debug("OHC: %s", os_e)
if 'single_files' in self.options and self.options['single_files']:
with open(cracked_file, 'r') as cracked_list:
for row in csv.DictReader(cracked_list):
if row['password']:
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap') ):
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
f.write(row['password'])

View File

@@ -3,8 +3,8 @@ 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
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemik and edited by shaynemk
GUIDE HERE: https://community.pwnagotchi.ai/t/setting-up-paw-gps-on-android
'''
@@ -18,14 +18,16 @@ class PawGPS(plugins.Plugin):
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)")
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1:8080)")
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"
ip = "192.168.44.1:8080"
else:
ip = self.options['ip']
gps = requests.get('http://' + ip + '/gps.xhtml')
gps_filename = filename.replace('.pcap', '.gps.json')
gps_filename = filename.replace('.pcap', '.paw-gps.json')
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
with open(gps_filename, 'w+t') as f:

View File

@@ -1,51 +0,0 @@
import logging
import subprocess
import string
import re
import pwnagotchi.plugins as plugins
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/)
Cracked handshakes stored in handshake folder as [essid].pcap.cracked
'''
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 __init__(self):
self.text_to_set = ""
def on_loaded(self):
logging.info("Quick dictionary check plugin loaded")
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)
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 = ""

View File

@@ -1,23 +0,0 @@
import logging
import pwnagotchi.plugins as plugins
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_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

View File

@@ -0,0 +1,263 @@
import os
import logging
import threading
from time import sleep
from datetime import datetime,timedelta
from pwnagotchi import plugins
from pwnagotchi.utils import StatusFile
from flask import render_template_string
from flask import jsonify
TEMPLATE = """
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Session stats
{% endblock %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="/css/jquery.jqplot.min.css"/>
<link rel="stylesheet" href="/css/jquery.jqplot.css"/>
<style>
div.chart {
height: 400px;
width: 100%;
}
div#session {
width: 100%;
}
</style>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="text/javascript" src="/js/jquery.jqplot.min.js"></script>
<script type="text/javascript" src="/js/jquery.jqplot.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.mobile.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.json2.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.dateAxisRenderer.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.highlighter.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.cursor.js"></script>
<script type="text/javascript" src="/js/plugins/jqplot.enhancedLegendRenderer.js"></script>
{% endblock %}
{% block script %}
$(document).ready(function(){
var ajaxDataRenderer = function(url, plot, options) {
var ret = null;
$.ajax({
async: false,
url: url,
dataType:"json",
success: function(data) {
ret = data;
}
});
return ret;
};
function loadFiles(url, elm) {
var data = ajaxDataRenderer(url);
var x = document.getElementById(elm);
$.each(data['files'], function( index, value ) {
var option = document.createElement("option");
option.text = value;
x.add(option);
});
}
function loadData(url, elm, title, fill) {
var data = ajaxDataRenderer(url);
var plot_os = $.jqplot(elm, data.values,{
title: title,
stackSeries: fill,
seriesDefaults: {
showMarker: !fill,
fill: fill,
fillAndStroke: fill
},
legend: {
show: true,
renderer: $.jqplot.EnhancedLegendRenderer,
placement: 'outsideGrid',
labels: data.labels,
location: 's',
rendererOptions: {
numberRows: '2',
},
rowSpacing: '0px'
},
axes:{
xaxis:{
renderer:$.jqplot.DateAxisRenderer,
tickOptions:{formatString:'%H:%M:%S'}
},
yaxis:{
tickOptions:{formatString:'%.2f'}
}
},
highlighter: {
show: true,
sizeAdjust: 7.5
},
cursor:{
show: true,
tooltipLocation:'sw'
}
}).replot({
axes:{
xaxis:{
renderer:$.jqplot.DateAxisRenderer,
tickOptions:{formatString:'%H:%M:%S'}
},
yaxis:{
tickOptions:{formatString:'%.2f'}
}
}
});
}
function loadSessionFiles() {
loadFiles('/plugins/session-stats/session', 'session');
$("#session").change(function() {
loadSessionData();
});
}
function loadSessionData() {
var x = document.getElementById("session");
var session = x.options[x.selectedIndex].text;
loadData('/plugins/session-stats/os' + '?session=' + session, 'chart_os', 'OS', false)
loadData('/plugins/session-stats/temp' + '?session=' + session, 'chart_temp', 'Temp', false)
loadData('/plugins/session-stats/wifi' + '?session=' + session, 'chart_wifi', 'Wifi', true)
loadData('/plugins/session-stats/duration' + '?session=' + session, 'chart_duration', 'Sleeping', true)
loadData('/plugins/session-stats/reward' + '?session=' + session, 'chart_reward', 'Reward', false)
loadData('/plugins/session-stats/epoch' + '?session=' + session, 'chart_epoch', 'Epochs', false)
}
loadSessionFiles();
loadSessionData();
setInterval(loadSessionData, 60000);
});
{% endblock %}
{% block content %}
<select id="session">
<option selected>Current</option>
</select>
<div id="chart_os" class="chart"></div>
<div id="chart_temp" class="chart"></div>
<div id="chart_wifi" class="chart"></div>
<div id="chart_duration" class="chart"></div>
<div id="chart_reward" class="chart"></div>
<div id="chart_epoch" class="chart"></div>
{% endblock %}
"""
class GhettoClock:
def __init__(self):
self.lock = threading.Lock()
self._track = datetime.now()
self._counter_thread = threading.Thread(target=self.counter)
self._counter_thread.daemon = True
self._counter_thread.start()
def counter(self):
while True:
with self.lock:
self._track += timedelta(seconds=1)
sleep(1)
def now(self):
with self.lock:
return self._track
class SessionStats(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin displays stats of the current session.'
def __init__(self):
self.lock = threading.Lock()
self.options = dict()
self.stats = dict()
self.clock = GhettoClock()
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
# this has to happen in "loaded" because the options are not yet
# available in the __init__
os.makedirs(self.options['save_directory'], exist_ok=True)
self.session_name = "stats_{}.json".format(self.clock.now().strftime("%Y_%m_%d_%H_%M"))
self.session = StatusFile(os.path.join(self.options['save_directory'],
self.session_name),
data_format='json')
logging.info("Session-stats plugin loaded.")
def on_epoch(self, agent, epoch, epoch_data):
"""
Save the epoch_data to self.stats
"""
with self.lock:
self.stats[self.clock.now().strftime("%H:%M:%S")] = epoch_data
self.session.update(data={'data': self.stats})
@staticmethod
def extract_key_values(data, subkeys):
result = dict()
result['values'] = list()
result['labels'] = subkeys
for plot_key in subkeys:
v = [ [ts,d[plot_key]] for ts, d in data.items()]
result['values'].append(v)
return result
def on_webhook(self, path, request):
if not path or path == "/":
return render_template_string(TEMPLATE)
session_param = request.args.get('session')
if path == "os":
extract_keys = ['cpu_load','mem_usage',]
elif path == "temp":
extract_keys = ['temperature']
elif path == "wifi":
extract_keys = [
'missed_interactions',
'num_hops',
'num_peers',
'tot_bond',
'avg_bond',
'num_deauths',
'num_associations',
'num_handshakes',
]
elif path == "duration":
extract_keys = [
'duration_secs',
'slept_for_secs',
]
elif path == "reward":
extract_keys = [
'reward',
]
elif path == "epoch":
extract_keys = [
'active_for_epochs',
]
elif path == "session":
return jsonify({'files': os.listdir(self.options['save_directory'])})
with self.lock:
data = self.stats
if session_param and session_param != 'Current':
file_stats = StatusFile(os.path.join(self.options['save_directory'], session_param), data_format='json')
data = file_stats.data_field_or('data', default=dict())
return jsonify(SessionStats.extract_key_values(data, extract_keys))

View File

@@ -0,0 +1,147 @@
import os
import logging
from threading import Lock
from functools import partial
from pwnagotchi import plugins
from pwnagotchi import reboot
def systemd_dropin(name, content):
if not name.endswith('.service'):
name = '%s.service' % name
dropin_dir = "/etc/systemd/system/%s.d/" % name
os.makedirs(dropin_dir, exist_ok=True)
with open(os.path.join(dropin_dir, "switcher.conf"), "wt") as dropin:
dropin.write(content)
systemctl("daemon-reload")
def systemctl(command, unit=None):
if unit:
os.system("/bin/systemctl %s %s" % (command, unit))
else:
os.system("/bin/systemctl %s" % command)
def run_task(name, options):
task_service_name = "switcher-%s-task.service" % name
# save all the commands to a shell script
script_dir = '/usr/local/bin/'
script_path = os.path.join(script_dir, 'switcher-%s.sh' % name)
os.makedirs(script_dir, exist_ok=True)
with open(script_path, 'wt') as script_file:
script_file.write('#!/bin/bash\n')
for cmd in options['commands']:
script_file.write('%s\n' % cmd)
os.system("chmod a+x %s" % script_path)
# here we create the service which runs the tasks
with open('/etc/systemd/system/%s' % task_service_name, 'wt') as task_service:
task_service.write("""
[Unit]
Description=Executes the tasks of the pwnagotchi switcher plugin
After=pwnagotchi.service bettercap.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/usr/local/bin/switcher-%s.sh
ExecStart=-/bin/rm /etc/systemd/system/%s
ExecStart=-/bin/rm /usr/local/bin/switcher-%s.sh
[Install]
WantedBy=multi-user.target
""" % (name, task_service_name, name))
if 'reboot' in options and options['reboot']:
# create a indication file!
# if this file is set, we want the switcher-tasks to run
open('/root/.switcher', 'a').close()
# add condition
systemd_dropin("pwnagotchi.service", """
[Unit]
ConditionPathExists=!/root/.switcher""")
systemd_dropin("bettercap.service", """
[Unit]
ConditionPathExists=!/root/.switcher""")
systemd_dropin(task_service_name, """
[Service]
ExecStart=-/bin/rm /root/.switcher
ExecStart=-/bin/rm /etc/systemd/system/switcher-reboot.timer""")
with open('/etc/systemd/system/switcher-reboot.timer', 'wt') as reboot_timer:
reboot_timer.write("""
[Unit]
Description=Reboot when time is up
ConditionPathExists=/root/.switcher
[Timer]
OnBootSec=%sm
Unit=reboot.target
[Install]
WantedBy=timers.target
""" % options['stopwatch'])
systemctl("daemon-reload")
systemctl("enable", "switcher-reboot.timer")
systemctl("enable", task_service_name)
reboot()
return
systemctl("daemon-reload")
systemctl("start", task_service_name)
class Switcher(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.0.1'
__name__ = 'switcher'
__license__ = 'GPL3'
__description__ = 'This plugin is a generic task scheduler.'
def __init__(self):
self.ready = False
self.lock = Lock()
def trigger(self, name, *args, **kwargs):
with self.lock:
function_name = name.lstrip('on_')
if function_name in self.tasks:
task = self.tasks[function_name]
# is this task enabled?
if 'enabled' not in task or ('enabled' in task and not task['enabled']):
return
run_task(function_name, task)
def on_loaded(self):
if 'tasks' in self.options and self.options['tasks']:
self.tasks = self.options['tasks']
else:
logging.debug('[switcher] No tasks found...')
return
logging.info("[switcher] is loaded.")
# create hooks
logging.debug("[switcher] creating hooks...")
methods = ['webhook', 'internet_available', 'ui_setup', 'ui_update',
'unload', 'display_setup', 'ready', 'ai_ready', 'ai_policy',
'ai_training_start', 'ai_training_step', 'ai_training_end',
'ai_best_reward', 'ai_worst_reward', 'free_channel',
'bored', 'sad', 'excited', 'lonely', 'rebooting', 'wait',
'sleep', 'wifi_update', 'unfiltered_ap_list', 'association',
'deauthentication', 'channel_hop', 'handshake', 'epoch',
'peer_detected', 'peer_lost', 'config_changed']
for m in methods:
setattr(Switcher, 'on_%s' % m, partial(self.trigger, m))
logging.debug("[switcher] triggers are ready to fire...")

View File

@@ -1,49 +0,0 @@
import logging
from pwnagotchi.voice import Voice
import pwnagotchi.plugins as plugins
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'
def on_loaded(self):
logging.info("twitter plugin loaded.")
# 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
if last_session.is_new() and last_session.handshakes > 0:
try:
import tweepy
except ImportError:
logging.error("Couldn't import tweepy")
return
logging.info("detected a new session and internet connectivity!")
picture = '/root/pwnagotchi.png'
display.on_manual_mode(last_session)
display.update(force=True)
display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
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")

View File

@@ -7,12 +7,14 @@
# 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
import logging
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
import pwnagotchi
# TODO: add enable switch in config.yml an cleanup all to the best place
@@ -55,8 +57,17 @@ class UPSLite(plugins.Plugin):
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),
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 + 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_unload(self, ui):
with ui._lock:
ui.remove_element('ups')
def on_ui_update(self, ui):
ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity()))
capacity = self.ups.capacity()
ui.set('ups', "%2i%%" % capacity)
if capacity <= self.options['shutdown']:
logging.info('[ups_lite] Empty battery (<= %s%%): shuting down' % self.options['shutdown'])
ui.update(force=True, new_data={'status': 'Battery exhausted, bye ...'})
pwnagotchi.shutdown()

View File

@@ -0,0 +1,516 @@
import logging
import json
import toml
import _thread
from pwnagotchi import restart, plugins
from pwnagotchi.utils import save_config
from flask import abort
from flask import render_template_string
INDEX = """
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Webcfg
{% endblock %}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=0" />
{% endblock %}
{% block styles %}
{{ super() }}
<style>
#divTop {
position: -webkit-sticky;
position: sticky;
top: 0px;
width: 100%;
font-size: 16px;
padding: 5px;
border: 1px solid #ddd;
margin-bottom: 5px;
}
#searchText {
width: 100%;
}
table {
table-layout: auto;
width: 100%;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
th, td {
padding: 15px;
text-align: left;
}
table tr:nth-child(even) {
background-color: #eee;
}
table tr:nth-child(odd) {
background-color: #fff;
}
table th {
background-color: black;
color: white;
}
.remove {
background-color: #f44336;
color: white;
border: 2px solid #f44336;
padding: 4px 8px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 12px;
margin: 4px 2px;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
cursor: pointer;
}
.remove:hover {
background-color: white;
color: black;
}
#btnSave {
position: -webkit-sticky;
position: sticky;
bottom: 0px;
width: 100%;
background-color: #0061b0;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
float: right;
}
#divTop {
display: table;
width: 100%;
}
#divTop > * {
display: table-cell;
}
#divTop > span {
width: 1%;
}
#divTop > input {
width: 100%;
}
@media screen and (max-width:700px) {
table, tr, td {
padding:0;
border:1px solid black;
}
table {
border:none;
}
tr:first-child, thead, th {
display:none;
border:none;
}
tr {
float: left;
width: 100%;
margin-bottom: 2em;
}
table tr:nth-child(odd) {
background-color: #eee;
}
td {
float: left;
width: 100%;
padding:1em;
}
td::before {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
.del_btn_wrapper {
content:attr(data-label);
word-wrap: break-word;
background: #eee;
border-right:2px solid black;
width: 20%;
float:left;
padding:1em;
font-weight: bold;
margin:-1em 1em -1em -1em;
}
}
</style>
{% endblock %}
{% block content %}
<div id="divTop">
<input type="text" id="searchText" placeholder="Search for options ..." title="Type an option name">
<span><select id="selAddType"><option value="text">Text</option><option value="number">Number</option></select></span>
<span><button id="btnAdd" type="button" onclick="addOption()">+</button></span>
</div>
<button id="btnSave" type="button" onclick="saveConfig()">Save and restart</button>
<div id="content"></div>
{% endblock %}
{% block script %}
function addOption() {
var input, table, tr, td, divDelBtn, btnDel, selType, selTypeVal;
input = document.getElementById("searchText");
inputVal = input.value;
selType = document.getElementById("selAddType");
selTypeVal = selType.options[selType.selectedIndex].value;
table = document.getElementById("tableOptions");
if (table) {
tr = table.insertRow();
// del button
divDelBtn = document.createElement("div");
divDelBtn.className = "del_btn_wrapper";
td = document.createElement("td");
td.setAttribute("data-label", "");
btnDel = document.createElement("Button");
btnDel.innerHTML = "X";
btnDel.onclick = function(){ delRow(this);};
btnDel.className = "remove";
divDelBtn.appendChild(btnDel);
td.appendChild(divDelBtn);
tr.appendChild(td);
// option
td = document.createElement("td");
td.setAttribute("data-label", "Option");
td.innerHTML = inputVal;
tr.appendChild(td);
// value
td = document.createElement("td");
td.setAttribute("data-label", "Value");
input = document.createElement("input");
input.type = selTypeVal;
input.value = "";
td.appendChild(input);
tr.appendChild(td);
input.value = "";
}
}
function saveConfig(){
// get table
var table = document.getElementById("tableOptions");
if (table) {
var json = tableToJson(table);
sendJSON("webcfg/save-config", json, function(response) {
if (response) {
if (response.status == "200") {
alert("Config got updated");
} else {
alert("Error while updating the config (err-code: " + response.status + ")");
}
}
});
}
}
var searchInput = document.getElementById("searchText");
searchInput.onkeyup = function() {
var filter, table, tr, td, i, txtValue;
filter = searchInput.value.toUpperCase();
table = document.getElementById("tableOptions");
if (table) {
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
}else{
tr[i].style.display = "none";
}
}
}
}
}
function sendJSON(url, data, callback) {
var xobj = new XMLHttpRequest();
var csrf = "{{ csrf_token() }}";
xobj.open('POST', url);
xobj.setRequestHeader("Content-Type", "application/json");
xobj.setRequestHeader('x-csrf-token', csrf);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4) {
callback(xobj);
}
};
xobj.send(JSON.stringify(data));
}
function loadJSON(url, callback) {
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', url, true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(JSON.parse(xobj.responseText));
}
};
xobj.send(null);
}
// https://stackoverflow.com/questions/19098797/fastest-way-to-flatten-un-flatten-nested-json-objects
function unFlattenJson(data) {
"use strict";
if (Object(data) !== data || Array.isArray(data))
return data;
var result = {}, cur, prop, idx, last, temp, inarray;
for(var p in data) {
cur = result, prop = "", last = 0, inarray = false;
do {
idx = p.indexOf(".", last);
temp = p.substring(last, idx !== -1 ? idx : undefined);
inarray = temp.startsWith('#') && !isNaN(parseInt(temp.substring(1)))
cur = cur[prop] || (cur[prop] = (inarray ? [] : {}));
if (inarray){
prop = temp.substring(1);
}else{
prop = temp;
}
last = idx + 1;
} while(idx >= 0);
cur[prop] = data[p];
}
return result[""];
}
function flattenJson(data) {
var result = {};
function recurse (cur, prop) {
if (Object(cur) !== cur) {
result[prop] = cur;
} else if (Array.isArray(cur)) {
for(var i=0, l=cur.length; i<l; i++)
recurse(cur[i], prop ? prop+".#"+i : ""+i);
if (l == 0)
result[prop] = [];
} else {
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop+"."+p : p);
}
if (isEmpty)
result[prop] = {};
}
}
recurse(data, "");
return result;
}
function delRow(btn) {
var tr = btn.parentNode.parentNode.parentNode;
tr.parentNode.removeChild(tr);
}
function jsonToTable(json) {
var table = document.createElement("table");
table.id = "tableOptions";
// create header
var tr = table.insertRow();
var thDel = document.createElement("th");
thDel.innerHTML = "";
var thOpt = document.createElement("th");
thOpt.innerHTML = "Option";
var thVal = document.createElement("th");
thVal.innerHTML = "Value";
tr.appendChild(thDel);
tr.appendChild(thOpt);
tr.appendChild(thVal);
var td, divDelBtn, btnDel;
// iterate over keys
Object.keys(json).forEach(function(key) {
tr = table.insertRow();
// del button
divDelBtn = document.createElement("div");
divDelBtn.className = "del_btn_wrapper";
td = document.createElement("td");
td.setAttribute("data-label", "");
btnDel = document.createElement("Button");
btnDel.innerHTML = "X";
btnDel.onclick = function(){ delRow(this);};
btnDel.className = "remove";
divDelBtn.appendChild(btnDel);
td.appendChild(divDelBtn);
tr.appendChild(td);
// option
td = document.createElement("td");
td.setAttribute("data-label", "Option");
td.innerHTML = key;
tr.appendChild(td);
// value
td = document.createElement("td");
td.setAttribute("data-label", "Value");
if(typeof(json[key])==='boolean'){
input = document.createElement("select");
input.setAttribute("id", "boolSelect");
tvalue = document.createElement("option");
tvalue.setAttribute("value", "true");
ttext = document.createTextNode("True")
tvalue.appendChild(ttext);
fvalue = document.createElement("option");
fvalue.setAttribute("value", "false");
ftext = document.createTextNode("False");
fvalue.appendChild(ftext);
input.appendChild(tvalue);
input.appendChild(fvalue);
input.value = json[key];
document.body.appendChild(input);
td.appendChild(input);
tr.appendChild(td);
} else {
input = document.createElement("input");
if(Array.isArray(json[key])) {
input.type = 'text';
input.value = '[]';
}else{
input.type = typeof(json[key]);
input.value = json[key];
}
td.appendChild(input);
tr.appendChild(td);
}
});
return table;
}
function tableToJson(table) {
var rows = table.getElementsByTagName("tr");
var i, td, key, value;
var json = {};
for (i = 0; i < rows.length; i++) {
td = rows[i].getElementsByTagName("td");
if (td.length == 3) {
// td[0] = del button
key = td[1].textContent || td[1].innerText;
var input = td[2].getElementsByTagName("input");
var select = td[2].getElementsByTagName("select");
if (input && input != undefined && input.length > 0 ) {
if (input[0].type == "text") {
if (input[0].value.startsWith("[") && input[0].value.endsWith("]")) {
json[key] = JSON.parse(input[0].value);
}else{
json[key] = input[0].value;
}
}else if (input[0].type == "number") {
json[key] = Number(input[0].value);
}
} else if(select && select != undefined && select.length > 0) {
var myValue = select[0].options[select[0].selectedIndex].value;
json[key] = myValue === 'true';
}
}
}
return unFlattenJson(json);
}
loadJSON("webcfg/get-config", function(response) {
var flat_json = flattenJson(response);
var table = jsonToTable(flat_json);
var divContent = document.getElementById("content");
divContent.innerHTML = "";
divContent.appendChild(table);
});
{% endblock %}
"""
def serializer(obj):
if isinstance(obj, set):
return list(obj)
raise TypeError
class WebConfig(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin allows the user to make runtime changes.'
def __init__(self):
self.ready = False
self.mode = 'MANU'
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_ready(self, agent):
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_internet_available(self, agent):
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("webcfg: Plugin loaded.")
def on_webhook(self, path, request):
"""
Serves the current configuration
"""
if not self.ready:
return "Plugin not ready"
if request.method == "GET":
if path == "/" or not path:
return render_template_string(INDEX)
elif path == "get-config":
# send configuration
return json.dumps(self.config, default=serializer)
else:
abort(404)
elif request.method == "POST":
if path == "save-config":
try:
save_config(request.get_json(), '/etc/pwnagotchi/config.toml') # test
_thread.start_new_thread(restart, (self.mode,))
return "success"
except Exception as ex:
logging.error(ex)
return "config error", 500
abort(404)

View File

@@ -0,0 +1,287 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/xml; charset=utf-8" />
<title>GPS MAP</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"/>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css" />
<script type='text/javascript' src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"></script>
<script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js'></script>
<style type="text/css">
/* for map */
html, body { height: 100%; width: 100%; margin:0; background-color:#000;}
.pwnAPPin path {
fill: #ce7575;
}
.pwnAPPinOpen path {
fill: #76ce75;
}
/* animated ap marker */
.pwnAPPin .ring_outer, .pwnAPPinOpen .ring_outer {
animation: opacityPulse 2s cubic-bezier(1, 0.14, 1, 1);
animation-iteration-count: infinite;
opacity: .5;
}
.pwnAPPin .ring_inner, .pwnAPPinOpen .ring_inner {
animation: opacityPulse 2s cubic-bezier(0.4, 0.74, 0.56, 0.82);
animation-iteration-count: infinite;
opacity: .8;
}
@keyframes opacityPulse {
0% {
opacity: 0.1;
}
50% {
opacity: 1.0;
}
100% {
opacity: 0.1;
}
}
@keyframes bounceInDown {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate3d(0, -3000px, 0);
}
60% {
opacity: 1;
transform: translate3d(0, 5px, 0);
}
75% {
transform: translate3d(0, -3px, 0);
}
90% {
transform: translate3d(0, 5px, 0);
}
to {
transform: none;
}
}
.bounceInDown {
animation-name: bounceInDown;
animation-duration: 2s;
animation-fill-mode: both;
}
/* animated radar */
.radar {
animation: pulsate 1s ease-out;
-webkit-animation: pulsate 1s ease-out;
-webkit-animation-iteration-count: infinite;
/* opacity: 0.0 */
}
#loading {
top: 50%;
left: 50%;
position: fixed;
background-color: rgba(255, 255, 255, 0.9);
border: 0.5vw #ff0000 solid;
border-radius: 2vw;
padding: 5vw;
transform: translateX(-50%) translateY(-50%);
text-align:center;
display: none;
}
#loading .face { font-size:8vw; }
#loading .text { position:absolute;bottom:0;text-align:center; font-size: 2vw;color:#a0a0a0;}
#filterbox { position: fixed;top:0px;left:0px;z-index:999999;margin-left:55px;width:100%;height:20px;border-bottom:2px solid #303030;display: grid;grid-template-columns: 1fr 0.1fr;grid-template-rows: 1fr;grid-template-areas: ". .";}
#search { grid-area: 1 / 1 / 2 / 2;height:30px;padding:3px;background-color:#000;color:#e0e0e0;border:none;}
#matchcount { grid-area: 1 / 2 / 2 / 3;height:30px;margin-right:55px;padding-right:5px;background-color:#000;color:#a0a0a0;font-weight:bold;}
#mapdiv { width:100%; height: 100%; }
</style>
</head>
<body>
<div id="mapdiv"></div>
<div id="filterbox">
<input type="text" id="search" placeholder="filter: #cracked #notcracked AA:BB:CC aabbcc AndroidAP ..."/>
<div id="matchcount">0&nbsp;APs</div>
</div>
<div id="loading"><div class="face"><nobr>(⌐■&nbsp;<span id="loading_ap_img"></span>&nbsp;■)</nobr></div><div class="text" id="loading_infotext">loading positions...</div></div>
<script type="text/javascript">
function loadJSON(url, callback) {
document.getElementById("loading").style.display = "flex";
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', url, true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
callback(xobj.responseText);
}
};
xobj.send(null);
}
function escapeHtml(unsafe) {
return String(unsafe)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function formatMacAddress(macAddress) {
if (macAddress !== null) {
macAddress = macAddress.toUpperCase();
if (macAddress.length >= 3 && macAddress.length <= 16) {
macAddress = macAddress.replace(/\W/ig, '');
macAddress = macAddress.replace(/(.{2})/g, "$1:");
macAddress = macAddress.replace(/:+$/,'');
}
}
return macAddress;
}
// select your map theme from https://leaflet-extras.github.io/leaflet-providers/preview/
// use 2 layers with alpha for a nice dark style
var Esri_WorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
});
var CartoDB_DarkMatter = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
opacity:0.8,
// maxZoom: 19
});
var mymap = L.map('mapdiv');
var svg = '<svg class="pwnAPPin" width="80px" height="60px" viewBox="0 0 44 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><desc>pwnagotchi AP icon.</desc><defs><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1"><stop stop-color="#FFFFFF" offset="0%"></stop><stop stop-color="#000000" offset="100%"></stop></linearGradient></defs><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="marker"><path class="ring_outer" d="M28.6,8 C34.7,9.4 39,12.4 39,16 C39,20.7 31.3,24.6 21.7,24.6 C12.1,24.6 4.3,20.7 4.3,16 C4.3,12.5 8.5,9.5 14.6,8.1 C15.3,8 14.2,6.6 13.3,6.8 C5.5,8.4 0,12.2 0,16.7 C0,22.7 9.7,27.4 21.7,27.4 C33.7,27.4 43.3,22.6 43.3,16.7 C43.3,12.1 37.6,8.3 29.6,6.7 C28.8,6.5 27.8,7.9 28.6,8.1 L28.6,8 Z" id="Shape" fill="#878787" fill-rule="nonzero"></path><path class="ring_inner" d="M28.1427313,11.0811939 C30.4951542,11.9119726 32.0242291,13.2174821 32.0242291,14.6416742 C32.0242291,17.2526931 27.6722467,19.2702986 22.261674,19.2702986 C16.8511013,19.2702986 12.4991189,17.2526931 12.4991189,14.7603569 C12.4991189,13.5735301 13.4400881,12.505386 15.0867841,11.6746073 C15.792511,11.3185592 14.7339207,9.30095371 13.9105727,9.77568442 C10.6171806,10.9625112 8.5,12.9801167 8.5,15.2350876 C8.5,19.0329333 14.4986784,22.0000002 21.9088106,22.0000002 C29.2013216,22.0000002 35.2,19.0329333 35.2,15.2350876 C35.2,12.861434 32.7299559,10.6064632 28.8484581,9.30095371 C28.0251101,9.18227103 27.4370044,10.8438285 28.0251101,11.0811939 L28.1427313,11.0811939 Z" id="Shape" fill="#5F5F5F" fill-rule="nonzero"></path><g id="ap" transform="translate(13.000000, 0.000000)"><rect id="apfront" fill="#000000" x="0" y="14" width="18" height="4"></rect><polygon id="apbody" fill="url(#linearGradient-1)" points="3.83034404 10 14.169656 10 18 14 0 14"></polygon><circle class="ring_outer" id="led1" fill="#931F1F" cx="3" cy="16" r="1"></circle><circle class="ring_inner" id="led2" fill="#931F1F" cx="7" cy="16" r="1"></circle><circle class="ring_outer" id="led3" fill="#931F1F" cx="11" cy="16" r="1"></circle><circle class="ring_inner" id="led4" fill="#931F1F" cx="15" cy="16" r="1"></circle><polygon id="antenna2" fill="#000000" points="8.8173082 0 9.1826918 0 9.5 11 8.5 11"></polygon><polygon id="antenna3" fill="#000000" transform="translate(15.000000, 5.500000) rotate(15.000000) translate(-15.000000, -5.500000) " points="14.8173082 0 15.1826918 0 15.5 11 14.5 11"></polygon><polygon id="antenna1" fill="#000000" transform="translate(3.000000, 5.500000) rotate(-15.000000) translate(-3.000000, -5.500000) " points="2.8173082 0 3.1826918 0 3.5 11 2.5 11"></polygon></g></g></g></svg>';
var svgOpen = '<svg class="pwnAPPinOpen" width="80px" height="60px" viewBox="0 0 44 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><desc>pwnagotchi AP icon.</desc><defs><linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1"><stop stop-color="#FFFFFF" offset="0%"></stop><stop stop-color="#000000" offset="100%"></stop></linearGradient></defs><g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="marker"><path class="ring_outer" d="M28.6,8 C34.7,9.4 39,12.4 39,16 C39,20.7 31.3,24.6 21.7,24.6 C12.1,24.6 4.3,20.7 4.3,16 C4.3,12.5 8.5,9.5 14.6,8.1 C15.3,8 14.2,6.6 13.3,6.8 C5.5,8.4 0,12.2 0,16.7 C0,22.7 9.7,27.4 21.7,27.4 C33.7,27.4 43.3,22.6 43.3,16.7 C43.3,12.1 37.6,8.3 29.6,6.7 C28.8,6.5 27.8,7.9 28.6,8.1 L28.6,8 Z" id="Shape" fill="#878787" fill-rule="nonzero"></path><path class="ring_inner" d="M28.1427313,11.0811939 C30.4951542,11.9119726 32.0242291,13.2174821 32.0242291,14.6416742 C32.0242291,17.2526931 27.6722467,19.2702986 22.261674,19.2702986 C16.8511013,19.2702986 12.4991189,17.2526931 12.4991189,14.7603569 C12.4991189,13.5735301 13.4400881,12.505386 15.0867841,11.6746073 C15.792511,11.3185592 14.7339207,9.30095371 13.9105727,9.77568442 C10.6171806,10.9625112 8.5,12.9801167 8.5,15.2350876 C8.5,19.0329333 14.4986784,22.0000002 21.9088106,22.0000002 C29.2013216,22.0000002 35.2,19.0329333 35.2,15.2350876 C35.2,12.861434 32.7299559,10.6064632 28.8484581,9.30095371 C28.0251101,9.18227103 27.4370044,10.8438285 28.0251101,11.0811939 L28.1427313,11.0811939 Z" id="Shape" fill="#5F5F5F" fill-rule="nonzero"></path><g id="ap" transform="translate(13.000000, 0.000000)"><rect id="apfront" fill="#000000" x="0" y="14" width="18" height="4"></rect><polygon id="apbody" fill="url(#linearGradient-1)" points="3.83034404 10 14.169656 10 18 14 0 14"></polygon><circle class="ring_outer" id="led1" fill="#1f9321" cx="3" cy="16" r="1"></circle><circle class="ring_inner" id="led2" fill="#1f9321" cx="7" cy="16" r="1"></circle><circle class="ring_outer" id="led3" fill="#1f9321" cx="11" cy="16" r="1"></circle><circle class="ring_inner" id="led4" fill="#1f9321" cx="15" cy="16" r="1"></circle><polygon id="antenna2" fill="#000000" points="8.8173082 0 9.1826918 0 9.5 11 8.5 11"></polygon><polygon id="antenna3" fill="#000000" transform="translate(15.000000, 5.500000) rotate(15.000000) translate(-15.000000, -5.500000) " points="14.8173082 0 15.1826918 0 15.5 11 14.5 11"></polygon><polygon id="antenna1" fill="#000000" transform="translate(3.000000, 5.500000) rotate(-15.000000) translate(-3.000000, -5.500000) " points="2.8173082 0 3.1826918 0 3.5 11 2.5 11"></polygon></g></g></g></svg>';
document.getElementById('loading_ap_img').innerHTML = svg;
var myIcon = L.divIcon({
className: "leaflet-data-marker",
html: svg.replace('#','%23'),
iconAnchor : [40, 30],
iconSize : [80, 60],
popupAnchor : [0, -30],
});
var myIconOpen = L.divIcon({
className: "leaflet-data-marker",
html: svgOpen.replace('#','%23'),
iconAnchor : [40, 30],
iconSize : [80, 60],
popupAnchor : [0, -30],
});
var positionsLoaded = false;
var positions = [];
var accuracys = [];
var markers = [];
var marker_pos = [];
var markerClusters = L.markerClusterGroup();
function drawPositions() {
count = 0;
//mymap.removeLayer(markerClusters);
mymap.eachLayer(function (layer) {
mymap.removeLayer(layer);
});
Esri_WorldImagery.addTo(mymap);
CartoDB_DarkMatter.addTo(mymap);
markerClusters = L.markerClusterGroup();
accuracys = [];
markers = [];
marker_pos = [];
filterText = document.getElementById("search").value;
//console.log(filterText);
Object.keys(positions).forEach(function(key) {
if(positions[key].lng){
filterPattern =
positions[key].ssid + ' ' +
formatMacAddress(positions[key].mac) + ' ' +
positions[key].mac
;
if (positions[key].pass) {
filterPattern += positions[key].pass + ' #cracked';
} else {
filterPattern += ' #notcracked';
}
filterPattern = filterPattern.toLowerCase();
//console.log(filterPattern);
var matched = true;
if (filterText) {
filterText.split(" ").forEach(function (item) {
if (!filterPattern.includes(item.toLowerCase())) {
matched = false;
}
});
}
if (matched) {
count++;
new_marker_pos = [positions[key].lat, positions[key].lng];
if (positions[key].acc) {
radius = Math.round(Math.min(positions[key].acc, 500));
markerColor = 'red';
markerColorCode = '#f03';
fillOpacity = 0.002;
if (positions[key].pass) {
markerColor = 'green';
markerColorCode = '#1aff00';
fillOpacity = 0.1;
}
accuracys.push(
L.circle(new_marker_pos, {
color: markerColor,
fillColor: markerColorCode,
fillOpacity: fillOpacity,
weight: 1,
opacity: 0.1,
radius: Math.min(positions[key].acc, 500),
}).setStyle({'className': 'radar'}).addTo(mymap)
);
}
passInfo = '';
if (positions[key].pass) {
passInfo = '<br/><b>Pass:</b> '+escapeHtml(positions[key].pass);
newMarker = L.marker(new_marker_pos, { icon: myIconOpen, title: positions[key].ssid }); //.addTo(mymap);
} else {
newMarker = L.marker(new_marker_pos, { icon: myIcon, title: positions[key].ssid }); //.addTo(mymap);
}
newMarker.bindPopup("<b>"+escapeHtml(positions[key].ssid)+"</b><br><nobr>MAC: "+escapeHtml(formatMacAddress(positions[key].mac))+"</nobr><br/>"+"<nobr>position type: "+escapeHtml(positions[key].type)+"</nobr><br/>"+"<nobr>position accuracy: "+escapeHtml(Math.round(positions[key].acc))+"</nobr>"+passInfo, { maxWidth: "auto" });
markers.push(newMarker);
marker_pos.push(new_marker_pos);
markerClusters.addLayer( newMarker );
}
}
});
document.getElementById("matchcount").innerHTML = count + "&nbsp;APs";
if (count > 0) {
mymap.addLayer( markerClusters );
var bounds = new L.LatLngBounds(marker_pos);
mymap.fitBounds(bounds);
document.getElementById("loading").style.display = "none";
} else {
document.getElementById("loading_infotext").innerHTML = "NO POSITION DATA FOUND :(";
}
}
// draw map on Enter in FilterInputField
const node = document.getElementById("search").addEventListener("keyup", function(event) {
if (event.key === "Enter") {
if (positionsLoaded) {
drawPositions();
}
}
});
// load positions
loadJSON("/plugins/webgpsmap/all", function(response) {
positions = JSON.parse(response);
positionsLoaded = true;
drawPositions();
});
// get current position and set marker in interval if https request
if (location.protocol === 'https:') {
var myLocationMarker = {};
function onLocationFound(e) {
if (myLocationMarker != undefined) {
mymap.removeLayer(myLocationMarker);
};
myLocationMarker = L.marker(e.latlng).addTo(mymap);
setTimeout(function(){ mymap.locate(); }, 30000);
}
mymap.on('locationfound', onLocationFound);
mymap.locate({setView: true});
}
</script>
</body></html>

Some files were not shown because too many files have changed in this diff Show More