262 Commits

Author SHA1 Message Date
Simone Margaritelli
c12b319287 releasing v1.5.4 2021-04-18 18:23:15 +02:00
Simone Margaritelli
712d0142a1 Merge pull request #919 from crahan/gps_config
Add configuration options to gps.py (position, vertical line spacing)
2021-04-18 17:43:15 +02:00
Thomas Bouve
9ba55b7f77 Merge updated 'master' into gps_config 2021-04-18 17:22:00 +02:00
Simone Margaritelli
abc16ce973 misc: updated bettercap and pwngrid versions in builder script 2021-04-18 17:17:31 +02:00
Simone Margaritelli
1318275b36 fix: using better symbols for ups charging status 2021-04-18 17:11:04 +02:00
Simone Margaritelli
297d9cd3b9 Merge pull request #905 from samuelkf/ups-lite-charging-status
Display UPS-Lite charging status
2021-04-18 17:08:37 +02:00
Simone Margaritelli
f164b8bb26 new: added new faces for uploading status 2021-04-18 17:03:21 +02:00
Simone Margaritelli
5c3b21f537 fix: recompiled localization files 2021-04-18 16:22:29 +02:00
Simone Margaritelli
6325428218 Merge pull request #903 from serginator/task/improve-es-lang
Improved Spanish translation
2021-04-18 16:00:44 +02:00
Simone Margaritelli
7d35f5cdc0 Merge branch 'master' into task/improve-es-lang 2021-04-18 16:00:37 +02:00
Simone Margaritelli
3fd3ac3c01 Merge pull request #911 from MannyLama/master
Improved Dutch translation
2021-04-18 15:59:13 +02:00
Simone Margaritelli
c1d3528ff7 Merge pull request #940 from ShaqKSmith/patch-1
Create voice.po
2021-04-18 15:58:59 +02:00
Simone Margaritelli
37fc65f834 Merge pull request #947 from theusu5k/patch-1
Update voice.po
2021-04-18 15:58:43 +02:00
Simone Margaritelli
80095edf4b Merge pull request #948 from MatthewNunu/master
Added Afrikaans translation
2021-04-18 15:56:45 +02:00
Simone Margaritelli
7e3e74635a Merge pull request #983 from xhrzg2017/patch-1
Update voice.po
2021-04-18 15:53:48 +02:00
Simone Margaritelli
6b3d9042fd Merge branch 'master' of github.com:evilsocket/pwnagotchi 2021-04-18 15:43:26 +02:00
Simone Margaritelli
4441ae852c misc: using stork for releases 2021-04-18 15:43:15 +02:00
Simone Margaritelli
edc0551e0a Merge pull request #933 from ajaswa/902/stop-using-uart-gpio-for-waveshare
swap out header file for one that doesn't tie up gpio pins 14 and 15 for waveshare_2
2021-04-18 15:16:23 +02:00
Simone Margaritelli
b6cf510f8e Merge pull request #876 from grokbeer/watchdog-reboot
Have watchdog plugin reboot system rather than restart application
2021-04-18 15:14:59 +02:00
Simone Margaritelli
7fb6be861a Merge pull request #909 from Tuinslak/patch-1
Make sure IPv4 works when eth0 is connected
2021-04-18 15:14:32 +02:00
Simone Margaritelli
f795598950 Merge pull request #918 from crahan/memtemp_gps_fixes
Add configuration options to memtemp.py (fields, position, vertical line spacing)
2021-04-18 15:13:23 +02:00
Simone Margaritelli
b0efd52961 Merge pull request #929 from HeroCC/master
Add donate option to Wigle plugin
2021-04-18 15:11:51 +02:00
Simone Margaritelli
38dfccb7c2 Merge pull request #953 from centraliota/master
Update wpa-sec.py logging
2021-04-18 15:11:10 +02:00
Simone Margaritelli
f912883f6e Merge pull request #967 from Xeyler/gps-bugfix
Fix display bug in gps module
2021-04-18 15:10:59 +02:00
Simone Margaritelli
661f26dedf Merge pull request #985 from Gadgetoid/patch-inky-ssd1608
Add support for SSD1608 variant 250x122 pixel Inky pHATs
2021-04-18 15:10:05 +02:00
Simone Margaritelli
d6c7a73f39 fix: fixed whl file urls 2021-04-18 15:07:16 +02:00
Simone Margaritelli
10f274dab7 fix: updated build scripts with latest packer version 2021-04-18 14:48:39 +02:00
Simone Margaritelli
decbeaccb1 Merge pull request #969 from jonspraggins/patch-1
Add Uploading 'Faces'
2021-04-18 13:18:43 +02:00
Philip Howard
34c2c8a06e Add support for SSD1608 variant 250x122 pixel Inky pHATs
Running `python3 -m inky.eeprom` should show display variant. Variants 10, 11 and 12 are SSD1608 based.

This patch adds support for auto-detecting and using them when "ui.display.color" is set to "auto".

Fixes #980 and #941

Signed-off-by: Philip Howard <phil@gadgetoid.com>
2021-04-09 10:23:59 +01:00
xhrzg2017
88a15528d6 Update voice.po 2021-04-09 10:06:58 +08:00
Jon Spraggins
0fd09878db Update faces.py
Added upload faces.
2021-03-18 11:47:25 -04:00
Brigham Campbell
c472e60615 Fix bug in gps module
Fix a bug caused by using a function in a conditional instead of the
return value of the function itself.

Signed-off-by: Brigham Campbell <me@brighamcampbell.com>
2021-03-14 11:46:04 -06:00
Simone Margaritelli
0704541dd1 Merge pull request #957 from FoxFromDarkness66/patch-1
Fix broken link in README
2021-03-14 16:29:03 +01:00
FoxFromDarkness66
ea061d473b Fix broken link in README
Default file is .toml now, not .yml
2021-01-25 20:00:21 +03:00
centraliota
6430a40847 Update wpa-sec.py logging
Added logging.info message for plugin loading, to keep consistent with behaviour of other plugins.
2020-11-17 18:01:52 -07:00
MatthewNunu
ba13b12593 added afrikaans lang 2020-11-12 12:05:29 +02:00
theusu5k
819be761ed Update voice.po
Some words were translated too literal.
Some menor corrections
2020-11-09 20:25:22 +01:00
ShaqKSmith
f701390d5a Create voice.po 2020-11-02 15:01:12 +08:00
Andrew Jaswa
2ddf040fac swap out header file for one that doesn't tie up gpio pins 14 and 15 for waveshare_2
Signed-off-by: Andrew Jaswa <andrew.jaswa@bonus.ly>
2020-10-09 21:13:31 -06:00
HeroCC
3bd9cd4f18 Tweak Wigle plugin
Adds a new 'donate' option to allow Wigle to include data for commercial use.

Also changes data header to reflect this is a pwnagotchi plugin.

Signed-off-by: HeroCC <herocc@herocc.com>
2020-10-03 17:06:29 +02:00
Thomas Bouve
5c6d8dc807 Adjust x coordinate for longer long field. 2020-09-20 22:12:54 +02:00
Thomas Bouve
c535ffd40e Fix version bump. 2020-09-20 21:24:33 +02:00
Thomas Bouve
fb7217b0fa Add configuration options and cleanup
- added support for configuring the plugin position and vertical line spacing
- fix default values for some displays which appeared to be incorrect
- change `ui.is_dfrobot_v2` to a function (to match other display entries)
- version bump to 1.0.2
2020-09-20 21:10:10 +02:00
Thomas Bouve
7da3cc5565 Revert. Changes are handled in a separate branch 2020-09-20 20:25:25 +02:00
Thomas Bouve
e72e2292a8 Update guessed coordinates based on other values. 2020-09-20 20:22:39 +02:00
Thomas Bouve
0f8643258e gps config updates 2020-09-20 11:14:00 +02:00
Thomas Bouve
1a0083eb38 Fix correct x positioning
Align fields right when less than 3 fields are shown in horizontal mode, using a 5-pixel character width with a 5-character width for each field (i.e. 25 pixels per field).
2020-09-20 01:43:52 +02:00
Thomas Bouve
72878454f9 Removed debug line. 2020-09-20 00:29:33 +02:00
Thomas Bouve
1c4df7a1c4 Additional comments 2020-09-20 00:27:26 +02:00
Thomas Bouve
c124a97514 Changelog update. 2020-09-20 00:25:52 +02:00
Thomas Bouve
b886b4e673 Added more configurable fields 2020-09-19 23:43:24 +02:00
Thomas Bouve
6d0e295276 Version bumps 2020-09-18 02:12:36 +02:00
Thomas Bouve
1aea0b95b1 Small gps fixes + memtemp rework 2020-09-18 02:11:37 +02:00
MannyLama
fddee8708e Updated dutch & fixed typos 2020-09-11 15:43:44 +02:00
MannyLama
ef4fbd96cc Updated dutch & fixed typos 2020-09-11 15:42:47 +02:00
Yeri Tiete
552df65422 Make sure IPv4 works when eth0 is connected
Resolves an issue where IPv4 traffic would not work when eth0 is connected (i.e. usb-ethernet adapter over OTG). The static route for usb0 precedes the DHCP route for eth0. Only IPv6 traffic would work, but not IPv4 until the metric for usb0 is increase and thus lowered in priority. If no eth0 is plugged in, internet works fine over usb0.
2020-09-04 17:49:41 +08:00
Sam Keating-Fry
2db8f143eb Display UPS-Lite charging status
Replace the '%' in the battery indicator with '' when the UPS-Lite is connected to an external power source.
Suggestions welcome for a different character as '' is a little hard to read on e-ink displays.
2020-08-29 14:40:44 +01:00
Sergio Ruiz
6111ee9d9d Improved Spanish translation
Signed-off-by: Sergio Ruiz <serginator@gmail.com>
2020-08-25 23:07:34 +02:00
Simone Margaritelli
713a14c504 Merge pull request #851 from MikeDawg/fixscripts
Fixing scripts - Make all scripts usage/help similar
2020-06-26 14:14:48 +02:00
Simone Margaritelli
2708fd032c Merge pull request #872 from grokbeer/shutdown-sync
Delaying fs sync to ensure shutdown message is logged to disk
2020-06-26 14:14:32 +02:00
Simone Margaritelli
f7cf4b3947 Merge pull request #874 from grokbeer/reboot-sync
Ensure fs is synced before reboot so logs aren't lost
2020-06-26 14:14:03 +02:00
Simone Margaritelli
1d312a727b Merge pull request #877 from grokbeer/fs-typo
Fix minor typo
2020-06-26 14:12:38 +02:00
Simone Margaritelli
dd3dbbe400 Merge pull request #882 from jacopotediosi/patch-1
Set maxZoom to max value
2020-06-26 14:12:15 +02:00
Simone Margaritelli
cfa034b555 Merge pull request #886 from elipsius/885/fix-2.7inch-waveshare-gps-display
Updated gps.py to include positioning for the waveshare 2.7 inch scre…
2020-06-26 14:11:17 +02:00
Simone Margaritelli
a0cd0d4936 Merge pull request #891 from voodoologic/master
If a step fails, abort script.
2020-06-26 14:10:52 +02:00
Simone Margaritelli
3e3fff298c Merge pull request #890 from BlackFrog1/laptop
Ignore python virtual environments
2020-06-26 14:10:33 +02:00
Doug
0b1c51dcc7 If a step fails, abort script. 2020-06-09 20:03:28 -07:00
BlackFrog1
8dd9a85615 quick ignore of python environments 2020-06-08 11:46:16 -04:00
Eric M Lipsius
929eac7bba Updated gps.py to include positioning for the waveshare 2.7 inch screen, tested on my hardware:
Raspberry Pi 3 /w waveshare 2.7 inch epaper hat, and USB GPS/GLONASS U-blox7

Signed-off-by: Eric M Lipsius <eric.lipsius@gmail.com>
2020-05-27 00:54:15 -04:00
Jacopo Tediosi
0f7870f770 Set maxZoom to max value
Why to limit zoom? As a user, I used sometimes to zoom so much.
2020-05-21 23:50:03 +02:00
grokbeer
37342c0685 Have watchdog plugin reboot system rather than restart application
Signed-off-by: grokbeer <1428380+grokbeer@users.noreply.github.com>
2020-05-08 23:17:12 +12:00
grokbeer
71514a97fa Delaying fs sync to ensure shutdown message is logged to disk
Signed-off-by: grokbeer <1428380+grokbeer@users.noreply.github.com>
2020-05-08 23:17:04 +12:00
grokbeer
03488819af Ensure fs is synced before reboot so logs aren't lost
Signed-off-by: grokbeer <1428380+grokbeer@users.noreply.github.com>
2020-05-08 23:16:54 +12:00
grokbeer
633b726bcd Fix minor typo
Signed-off-by: grokbeer <1428380+grokbeer@users.noreply.github.com>
2020-05-08 23:16:32 +12:00
Simone Margaritelli
05b235c38b Merge pull request #860 from misterf13/master
Display coordinates for dfrobotv2
2020-05-06 12:04:36 +02:00
Ferny G
840054f549 Display coordinates for dfrobotv2
Signed-off-by: misterf13 <fgarcia1337@gmail.com>
2020-04-20 22:40:39 -05:00
Simone Margaritelli
a3cf49244e Merge pull request #855 from dadav/develop
Bug fixes
2020-04-20 11:13:25 +02:00
dadav
e92751164f typo 2020-04-19 18:49:42 +02:00
dadav
c2f9860bc5 same as in the py file 2020-04-19 16:48:11 +02:00
dadav
f616871068 fix syntax 2020-04-19 16:46:34 +02:00
dadav
c726779f6e only go back 5 mins 2020-04-19 16:36:26 +02:00
dadav
a2e29d64d2 dont overwrite zips 2020-04-19 11:39:49 +02:00
dadav
bc84f22f7a fix ident 2020-04-19 11:37:48 +02:00
dadav
40d8d994d1 version++ 2020-04-19 11:34:39 +02:00
dadav
7ca5eee247 add custom repos 2020-04-19 11:26:18 +02:00
dadav
56c291d302 change treshhold to 5 2020-04-18 18:19:16 +02:00
dadav
1013e7dc19 make less verbose 2020-04-18 17:09:39 +02:00
dadav
ff4f5c672a move code out of ui update hook 2020-04-18 16:46:29 +02:00
dadav
d9d268ea81 fix bug 2020-04-18 13:53:42 +02:00
dadav
de62214970 add missing option 2020-04-18 13:30:43 +02:00
dadav
52cc413152 add check 2020-04-18 13:23:12 +02:00
dadav
716d5cd312 typo 2020-04-18 13:17:18 +02:00
dadav
e436dc8b8e fix 2020-04-18 13:02:42 +02:00
dadav
81db495f33 redirect to dashboard 2020-04-18 12:47:09 +02:00
dadav
7f8380c38a switch to static 2020-04-18 12:26:21 +02:00
Ferny G
8c2b4e2075 DFRobot V2 screen 2020-04-18 11:40:43 +02:00
dadav
2b17e5322b only kill if present 2020-04-18 11:29:54 +02:00
dadav
1be17b1f99 reboot if reload fails 2020-04-18 11:17:46 +02:00
dadav
2dee3987d4 add bash completion 2020-04-18 10:59:08 +02:00
dadav
eb76cc7023 fix location 2020-04-18 10:22:18 +02:00
dadav
35ea36ef33 auto redirect when decrypted 2020-04-18 10:09:20 +02:00
dadav
44e1e79245 scorp fix 2020-04-18 09:50:40 +02:00
dadav
6038f555fa fix 2020-04-18 00:02:25 +02:00
dadav
0b5a63a3d8 add missing var 2020-04-17 22:31:30 +02:00
dadav
430172e3dd remove button 2020-04-17 20:55:11 +02:00
dadav
fa87e03222 add max-lines 2020-04-17 20:18:12 +02:00
dadav
d1411ffa96 sort plugins 2020-04-17 16:45:04 +02:00
dadav
67b4747afa adjust cmd 2020-04-17 16:39:31 +02:00
dadav
311931c81d added watchdog 2020-04-17 16:32:24 +02:00
Simone Margaritelli
1f7bc60de6 Merge pull request #848 from Czechball/master
added Czech translation
2020-04-16 13:01:17 +02:00
Simone Margaritelli
57034d9fc6 Merge pull request #852 from dadav/develop
Various fixes
2020-04-16 13:01:08 +02:00
dadav
74fbf4da32 version++ 2020-04-16 10:31:59 +02:00
dadav
7ec20caf23 fix filter bug 2020-04-15 17:49:57 +02:00
dadav
568c5b020d aaaannnd even better 2020-04-15 17:19:41 +02:00
dadav
3965bdb554 performs better without it 2020-04-15 17:02:50 +02:00
dadav
585b208e9e support multiple passwords 2020-04-15 16:51:43 +02:00
dadav
e53bdc46a4 there is no logging to journald anymore 2020-04-15 13:19:01 +02:00
dadav
6805df858e basename should be dirname 2020-04-15 08:20:47 +02:00
dadav
8a07e822e6 breaks if never used 2020-04-14 22:20:42 +02:00
MikeDawg
5f7dd56ead Fixing scripts
Signed-off-by: MikeDawg <MikeDawg@gmail.com>
2020-04-14 14:08:16 -06:00
dadav
a808fd33c7 another toml fix in paradise 2020-04-14 21:53:17 +02:00
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
Czechball
68065d548a added Czech translation 2020-04-14 06:17:57 +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
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
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
169 changed files with 6878 additions and 1538 deletions

View File

@@ -1,7 +1,31 @@
# top-most EditorConfig file
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
# Matches the exact files either package.json or .travis.yml
[{*.yml,*.yaml,config.yml,defaults.yml}]
[*]
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

10
.gitignore vendored
View File

@@ -17,3 +17,13 @@ dist
pwnagotchi.egg-info
*backup*.tgz
*backup*.gz
.vscode
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

View File

@@ -1,10 +1,17 @@
PACKER_VERSION=1.7.2
PWN_HOSTNAME=pwnagotchi
PWN_VERSION=master
all: clean install image
langs:
@for lang in pwnagotchi/locale/*/; do\
echo "compiling language: $$lang ..."; \
./scripts/language.sh compile $$(basename $$lang); \
done
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

@@ -15,7 +15,7 @@ full and half WPA handshakes.
![ui](https://i.imgur.com/X68GXrn.png)
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning-based "AI" *(yawn)*, Pwnagotchi tunes [its parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.toml) over time to **get better at pwning WiFi things to** in the environments you expose it to.
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)

View File

@@ -2,24 +2,23 @@
import logging
import argparse
import time
import yaml
import signal
import sys
import toml
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
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()
exit(0)
sys.exit(0)
def do_manual_mode(agent):
@@ -84,15 +83,16 @@ def do_auto_mode(agent):
plugins.on('internet_available', agent)
except Exception as e:
logging.exception("main loop exception")
logging.exception("main loop exception (%s)", e)
if __name__ == '__main__':
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.")
@@ -113,16 +113,34 @@ if __name__ == '__main__':
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)
exit(0)
print(pwnagotchi.__version__)
sys.exit(0)
config = utils.load_config(args)
if args.print_config:
print(yaml.dump(config, default_flow_style=False))
exit(0)
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'])
@@ -132,7 +150,7 @@ if __name__ == '__main__':
if args.do_clear:
do_clear(display)
exit(0)
sys.exit(0)
agent = Agent(view=display, config=config, keypair=KeyPair(view=display))

View File

@@ -0,0 +1,29 @@
_show_complete()
{
local cur opts node_names all_options opt_line
all_options="
pwnagotchi -h --help -C --config -U --user-config --manual --skip-session --clear --debug --version --print-config {plugins}
pwnagotchi plugins -h --help {list,install,enable,disable,uninstall,update,upgrade}
pwnagotchi plugins list -i --installed -h --help
pwnagotchi plugins install -h --help
pwnagotchi plugins uninstall -h --help
pwnagotchi plugins enable -h --help
pwnagotchi plugins disable -h --help
pwnagotchi plugins update -h --help
pwnagotchi plugins upgrade -h --help
"
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
cmd="${COMP_WORDS[@]:0:${#COMP_WORDS[@]}-1}"
opt_line="$(grep -m1 "$cmd" <<<$all_options)"
if [[ ${cur} == -* ]] ; then
opts="$(echo $opt_line | tr ' ' '\n' | awk '/^ *-/{gsub("[^a-zA-Z0-9-]","",$1);print $1}')"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
opts="$(echo $opt_line | grep -Po '{\K[^}]+' | tr ',' '\n')"
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
}
complete -F _show_complete pwnagotchi

View File

@@ -4,4 +4,5 @@ iface usb0 inet static
netmask 255.255.255.0
network 10.0.0.0
broadcast 10.0.0.255
gateway 10.0.0.1
gateway 10.0.0.1
metric 20

View File

@@ -6,12 +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

View File

@@ -1,6 +1,23 @@
#!/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
# check if wifi driver is bugged
if ! check_brcm; then
if ! reload_brcm; then
echo "Could not reload wifi driver. Reboot"
reboot
fi
sleep 10
fi
# start mon0
start_monitor_interface

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import parse_qsl
_HTML_FORM_TEMPLATE = """
<!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">
{password_fields}
<input type="submit" value="Submit">
</form>
</div>
</article>
</body>
</html>
"""
POST_RESPONSE = """
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
/* Center the loader */
#loader {
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#myDiv {
display: none;
text-align: center;
}
</style>
<script type="text/javascript">
function checkPwnagotchi() {
var target = 'http://' + document.location.hostname + ':8080/';
var xhr = new XMLHttpRequest();
xhr.open('GET', target);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 401) {
window.location.replace(target);
}else{
setTimeout(checkPwnagotchi, 1000);
}
}
};
xhr.send();
}
setTimeout(checkPwnagotchi, 1000);
</script>
</head>
<body style="margin:0;">
<div id="loader"></div>
</body>
</html>
"""
HTML_FORM = None
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)
for mapping, password in parse_qsl(body.decode('UTF-8')):
with open('/tmp/.pwnagotchi-secret-{}'.format(mapping), 'wt') as pwfile:
pwfile.write(password)
self.send_response(200)
self.end_headers()
self.wfile.write(POST_RESPONSE.encode())
with open('/root/.pwnagotchi-crypted') as crypted_file:
mappings = [line.split()[0] for line in crypted_file.readlines()]
fields = ''.join(['<label for="{m}">Passphrase for {m}:</label>\n<input type="password" id="{m}" name="{m}" value=""><br>'.format(m=m)
for m in mappings])
HTML_FORM = _HTML_FORM_TEMPLATE.format(password_fields=fields)
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

@@ -12,9 +12,28 @@ blink_led() {
sleep 0.3
}
# check if brcm is stuck
check_brcm() {
if [[ "$(journalctl -n10 -k --since -5m | grep -c 'brcmf_cfg80211_nexmon_set_channel.*Set Channel failed')" -ge 5 ]]; then
return 1
fi
return 0
}
# reload mod
reload_brcm() {
if ! modprobe -r brcmfmac; then
return 1
fi
if ! modprobe brcmfmac; then
return 1
fi
return 0
}
# starts mon0
start_monitor_interface() {
iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up
iw phy "$(iw phy | head -1 | cut -d" " -f2)" interface add mon0 type monitor && ifconfig mon0 up
}
# stops mon0
@@ -84,4 +103,82 @@ 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-"$mapping" ]; then
</tmp/.pwnagotchi-secret-"$mapping" 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 passwords
python3 -c 'print("A"*4096)' | tee /tmp/.pwnagotchi-secret-* >/dev/null
# delete
rm /tmp/.pwnagotchi-secret-*
sync # flush
pkill wpa_supplicant
pkill dnsmasq
pid="$(pgrep -f "decryption-webserver")"
[[ -n "$pid" ]] && kill "$pid"
return 0
}

View File

@@ -4,7 +4,6 @@
"name": "pwnagotchi",
"type": "arm-image",
"iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip",
"iso_checksum_type": "sha256",
"iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e",
"last_partition_extra_size": 3221225472
}
@@ -98,6 +97,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

@@ -35,10 +35,10 @@
- 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.31.0/bettercap_linux_armhf_v2.31.0.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"
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.10.3/pwngrid_linux_armhf_v1.10.3.zip"
apt:
hold:
- firmware-atheros
@@ -47,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
@@ -100,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
@@ -177,7 +178,7 @@
chdir: /usr/local/src/gratis
target: rpi
params:
EPD_IO: epd_io.h
EPD_IO: epd_io_free_uart.h
PANEL_VERSION: 'V231_G2'
when: gratisgit.changed
@@ -186,7 +187,7 @@
chdir: /usr/local/src/gratis
target: rpi-install
params:
EPD_IO: epd_io.h
EPD_IO: epd_io_free_uart.h
PANEL_VERSION: 'V231_G2'
when: gratisgit.changed
@@ -216,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:
@@ -228,17 +239,17 @@
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:
name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
name: "https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['opencv-python'] is undefined) or (pip_packages['opencv-python'] != '3.4.3.18')
- name: install tensorflow
pip:
name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
name: "https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
when: (pip_packages['tensorflow'] is undefined) or (pip_packages['tensorflow'] != '1.13.1')
@@ -246,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:
@@ -300,21 +311,17 @@
- name: check if user configuration exists
stat:
path: /etc/pwnagotchi/config.yml
path: /etc/pwnagotchi/config.toml
register: user_config
- name: create /etc/pwnagotchi/config.yml
- name: create /etc/pwnagotchi/config.toml
copy:
dest: /etc/pwnagotchi/config.yml
dest: /etc/pwnagotchi/config.toml
content: |
# Add your configuration overrides on this file any configuration changes done to default.yml will be lost!
# Add your configuration overrides on this file any configuration changes done to default.toml will be lost!
# Example:
#
# ui:
# display:
# type: 'inkyphat'
# color: 'black'
#
# ui.display.enabled = true
# ui.display.type = "waveshare_2"
when: not user_config.stat.exists
- name: enable ssh on boot
@@ -361,15 +368,15 @@
Hi! I'm a pwnagotchi, please take good care of me!
Here are some basic things you need to know to raise me properly!
If you want to change my configuration, use /etc/pwnagotchi/config.yml
If you want to change my configuration, use /etc/pwnagotchi/config.toml
All the configuration options can be found on /etc/pwnagotchi/default.yml,
All the configuration options can be found on /etc/pwnagotchi/default.toml,
but don't change this file because I will recreate it every time I'm restarted!
I'm managed by systemd. Here are some basic commands.
If you want to know what I'm doing, you can check my logs with the command
journalctl -fu pwnagotchi
tail -f /var/log/pwnagotchi.log
If you want to know if I'm running, you can use
systemctl status pwnagotchi

View File

@@ -3,12 +3,12 @@ import logging
import time
import re
import pwnagotchi.ui.view as view
import pwnagotchi
version = '1.4.2'
from pwnagotchi._version import __version__
_name = None
config = None
def set_name(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):
@@ -100,10 +107,19 @@ def temperature(celsius=True):
def shutdown():
logging.warning("shutting down ...")
from pwnagotchi.ui import view
if view.ROOT:
view.ROOT.on_shutdown()
# give it some time to refresh the ui
time.sleep(10)
logging.warning("syncing...")
from pwnagotchi import fs
for m in fs.mounts:
m.sync()
os.system("sync")
os.system("halt")
@@ -127,6 +143,7 @@ def reboot(mode=None):
else:
logging.warning("rebooting ...")
from pwnagotchi.ui import view
if view.ROOT:
view.ROOT.on_rebooting()
# give it some time to refresh the ui
@@ -137,5 +154,11 @@ def reboot(mode=None):
elif mode == 'MANU':
os.system("touch /root/.pwnagotchi-manual")
logging.warning("syncing...")
from pwnagotchi import fs
for m in fs.mounts:
m.sync()
os.system("sync")
os.system("shutdown -r now")

1
pwnagotchi/_version.py Normal file
View File

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

View File

@@ -3,6 +3,7 @@ import json
import os
import re
import logging
import asyncio
import _thread
import pwnagotchi
@@ -30,7 +31,7 @@ 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
@@ -49,7 +50,7 @@ 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)
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__)
@@ -68,7 +69,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
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):
@@ -121,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)
@@ -134,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()
@@ -158,7 +160,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
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)
@@ -188,7 +190,7 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
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)
@@ -303,60 +305,68 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
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
s = self.session()
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, %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)
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.get_event_loop(),))
def is_module_running(self, module):
s = self.session()
@@ -465,4 +475,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

@@ -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

@@ -120,14 +120,14 @@ class Automata(object):
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']:

View File

@@ -1,5 +1,8 @@
import json
import logging
import requests
import websockets
from requests.auth import HTTPBasicAuth
@@ -25,15 +28,27 @@ 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...")
except websockets.exceptions.WebSocketException as wex:
logging.debug("Websocket exception (%s)", wex)
def run(self, command, verbose_errors=True):
r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command})

238
pwnagotchi/defaults.toml Normal file
View File

@@ -0,0 +1,238 @@
main.name = ""
main.lang = "en"
main.confd = "/etc/pwnagotchi/conf.d/"
main.custom_plugins = ""
main.custom_plugin_repos = [
"https://github.com/evilsocket/pwnagotchi-plugins-contrib/archive/master.zip"
]
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.wigle.donate = true
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.logtail.max-lines = 10000
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.faces.upload = "(1__0)"
ui.faces.upload1 = "(1__1)"
ui.faces.upload2 = "(0__1)"
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,295 +0,0 @@
#
# 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
net-pos:
enabled: false
api_key: 'test'
gps:
enabled: false
speed: 19200
device: /dev/ttyUSB0
webgpsmap:
enabled: false
onlinehashcrack:
enabled: false
email: ~
wpa-sec:
enabled: false
api_key: ~
api_url: "https://wpa-sec.stanev.org"
download_results: false
wigle:
enabled: false
api_key: ~
bt-tether:
enabled: false # if you want to use this, set ui.display.web.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
scale: celsius
orientation: horizontal # horizontal/vertical
paw-gps:
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: 'touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi'
#21: 'shutdown -h now'
led:
enabled: true
# for /sys/class/leds/led0/brightness
led: 0
# time in milliseconds for each element of the patterns
delay: 200
# o=on space=off, comment the ones you don't want
patterns:
loaded: 'oo oo oo oo oo oo oo'
updating: 'oo oo oo oo oo oo oo'
# internet_available: 'oo oo oo oo oo oo oo'
unread_inbox: 'oo oo oo oo oo oo oo'
ready: 'oo oo oo oo oo oo oo'
ai_ready: 'oo oo oo oo oo oo oo'
ai_training_start: 'oo oo oo oo oo oo oo'
ai_best_reward: 'oo oo oo oo oo oo oo'
ai_worst_reward: 'oo oo oo oo oo oo oo'
bored: 'oo oo oo oo oo oo oo'
sad: 'oo oo oo oo oo oo oo'
excited: 'oo oo oo oo oo oo oo'
lonely: 'oo oo oo oo oo oo oo'
rebooting: 'oo oo oo oo oo oo oo'
wait: 'oo oo oo oo oo oo oo'
sleep: 'oo oo oo oo oo oo oo'
wifi_update: 'oo oo oo oo oo oo oo'
association: 'oo oo oo oo oo oo oo'
deauthentication: 'oo oo oo oo oo oo oo'
handshake: 'oo oo oo oo oo oo oo'
epoch: 'oo oo oo oo oo oo oo'
peer_detected: 'oo oo oo oo oo oo oo'
peer_lost: 'oo oo oo oo oo oo oo'
webcfg:
enabled: false
session-stats:
enabled: true
# 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
# if true, will not restart the wifi module
no_restart: false
# access points to ignore. Could be the ssid, bssid or the vendor part of bssid.
whitelist:
- EXAMPLE_NETWORK
- ANOTHER_EXAMPLE_NETWORK
- fo:od:ba:be:fo:od # BSSID
- fo:od:ba # Vendor BSSID
# if not null, filter access points by this regular expression
filter: null
# logging
log:
# file to log to
path: /var/log/pwnagotchi.log
rotation:
enabled: true
# specify a maximum size to rotate ( format is 10/10B, 10K, 10M 10G )
size: '10M'
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
# web ui
web:
enabled: true
address: '0.0.0.0'
username: changeme # !!! CHANGE THIS !!!
password: changeme # !!! CHANGE THIS !!!
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: ''
# hardware display
display:
enabled: true
rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat, lcdhat, waveshare154inch, waveshare27inch, waveshare29inch, dfrobot/df, waveshare144lcd/ws144inch
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'
# 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] Syncing %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

@@ -85,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)

Binary file not shown.

View File

@@ -0,0 +1,248 @@
# Afrikaans translation of pwnagotchi.
# Copyright (C) 2020.
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR MatthewNunu https://github.com/MatthewNunu, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: 1.5.3\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-29 21:50+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: MatthewNunu https://github.com/MatthewNunu\n"
"Language-Team: \n"
"Language: Afrikaans\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 "Hi, ek is Pwnagotchi! Aanvang ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nuwe dag, nuwe jag, nuwe pwns!"
msgid "Hack the Planet!"
msgstr "Hack die wêreld!"
msgid "AI ready."
msgstr "AI gereed."
msgid "The neural network is ready."
msgstr "Die neurale netwerk is gereed."
msgid "Generating keys, do not turn off ..."
msgstr "Genereer wagwoord, moenie afskakel nie ..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Haai, kanaal {channel} is gratis! Jou AP sal dankie sê."
msgid "Reading last session logs ..."
msgstr "Lees laaste sessie logs ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Ek het {lines_so_far} tot dusver gelees ..."
msgid "I'm bored ..."
msgstr "Ek's verveeld ..."
msgid "Let's go for a walk!"
msgstr "Kom ons gaan vir 'n loopie!"
msgid "This is the best day of my life!"
msgstr "Dit is die beste dag van my lewe!"
msgid "Shitty day :/"
msgstr "Poes kak dag :/"
msgid "I'm extremely bored ..."
msgstr "Ek's baie verveeld ..."
msgid "I'm very sad ..."
msgstr "Ek's baie hartseer ..."
msgid "I'm sad"
msgstr "Ek's hartseer ..."
msgid "Leave me alone ..."
msgstr "Los my uit ..."
msgid "I'm mad at you!"
msgstr "Ek is kwaad vir jou!"
msgid "I'm living the life!"
msgstr "Ek leef die lewe!"
msgid "I pwn therefore I am."
msgstr "Ek pwn daarom is ek."
msgid "So many networks!!!"
msgstr "Soveel netwerke!!!"
msgid "I'm having so much fun!"
msgstr "Ek het soveel pret!"
msgid "My crime is that of curiosity ..."
msgstr "My misdaad is dié van nuuskierigheid ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hallo {name}! Lekker om jou te ontmoet."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Yo {name}! Sup?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Haai {name} hoe doen jy?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Eenheid {name}} is naby!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... totsiens {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} is weg ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ... {name} is weg."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} gemis!"
msgid "Missed!"
msgstr "Gemis!"
msgid "Good friends are a blessing!"
msgstr "Goeie vriende is 'n seën!"
msgid "I love my friends!"
msgstr "Ek is lief vir my vriende!"
msgid "Nobody wants to play with me ..."
msgstr "Niemand wil met my speel nie ..."
msgid "I feel so alone ..."
msgstr "Ek voel so alleen ..."
msgid "Where's everybody?!"
msgstr "Waar is almal?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Slaap vir {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Goeie nag."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Wag tans vir {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Rondkyk ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Haai {what} kom ons wees vriende!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Assosieer na {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Yo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Net besluit dat {mac} geen WiFi nodig het nie!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Deauthenticating {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanning {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Koel, ons het {num} nuwe handdruk gekry!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Jy het {count} nuwe boodskap!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, iets het verkeerd gegaan ... Herlaai ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Geskop {num} stasies\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Gemaak {num} nuwe vriende\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Het {num} handdrukke\n"
msgid "Met 1 peer"
msgstr "Ontmoet 1 eweknie"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Ontmoet {num} eweknie"
#, 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 ""
"Ek was pwning vir {duration} en het {deauthed} kliënte geskop! Ek het ook ontmoet "
"{associated} nuwe vriende en het {handshakes} handdrukke geëet! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "uur"
msgid "minutes"
msgstr "minute"
msgid "seconds"
msgstr "sekondes"
msgid "hour"
msgstr "uur"
msgid "minute"
msgstr "minuut"
msgid "second"
msgstr "tweede"

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

@@ -40,7 +40,7 @@ msgstr "创建密钥中, 请勿断电..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr ""
msgstr "嘿,频道{channel}是免费的你的AP会说谢谢。"
msgid "I'm bored ..."
msgstr "我无聊了..."
@@ -84,7 +84,7 @@ msgstr "你好{name}!很高兴认识你."
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr ""
msgstr "小队{name}就在附近!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
@@ -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
@@ -193,7 +193,7 @@ msgid "Got {num} handshakes\n"
msgstr "捕获了{num}握手包\n"
msgid "Met 1 peer"
msgstr ""
msgstr "有{num}同龄人"
#, python-brace-format
msgid "Met {num} peers"

Binary file not shown.

View File

@@ -0,0 +1,249 @@
# pwnigotchi voice data
# Copyright (C) 2020
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR czechball@users.noreply.github.com, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-14 06:15+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Czechball <czechball@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
"Language: cs\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, já jsem Pwnagotchi! Startuju ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nový den, nový lov, nové úlovky!"
msgid "Hack the Planet!"
msgstr "Hackni celou planetu!"
msgid "AI ready."
msgstr "AI připraveno."
msgid "The neural network is ready."
msgstr "Neuronová síť je připravena."
msgid "Generating keys, do not turn off ..."
msgstr "Generování klíčů, nevypínej mě..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanál {channel} je volný! Tvůj AP ti poděkuje."
msgid "Reading last session logs ..."
msgstr "Čtení posledních zpráv z logu ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Zatím přečteno {lines_so_far} řádků logu ..."
msgid "I'm bored ..."
msgstr "Nudím se ..."
msgid "Let's go for a walk!"
msgstr "Pojďme se projít!"
msgid "This is the best day of my life!"
msgstr "Tohle je nejlepší den mého života!"
msgid "Shitty day :/"
msgstr "Na hovno den :/"
msgid "I'm extremely bored ..."
msgstr "Strašně se nudím ..."
msgid "I'm very sad ..."
msgstr "Jsem dost smutný ..."
msgid "I'm sad"
msgstr "Jsem smutný"
msgid "Leave me alone ..."
msgstr "Nech mě být ..."
msgid "I'm mad at you!"
msgstr "Jsem na tebe naštvaný!"
msgid "I'm living the life!"
msgstr "Tohle je život!"
msgid "I pwn therefore I am."
msgstr "Chytám pakety a tedy jsem."
msgid "So many networks!!!"
msgstr "Tolik sítí!!!"
msgid "I'm having so much fun!"
msgstr "Tohle je super zábava!"
msgid "My crime is that of curiosity ..."
msgstr "Jsem kriminálně zvědavý ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Ahoj {name}! Rád tě poznávám."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Hej {name}! Jak to jde?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Ahoj {name}, jak se máš?"
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Jednotka {name} je nablízku!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm... Měj se {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} je pryč ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoops ... {name} je pryč."
#, python-brace-format
msgid "{name} missed!"
msgstr "Chybí mi {name}!"
msgid "Missed!"
msgstr "Chybíš mi!"
msgid "Good friends are a blessing!"
msgstr "Dobří kamarádi jsou požehnání!"
msgid "I love my friends!"
msgstr "Miluju svoje kamarády!"
msgid "Nobody wants to play with me ..."
msgstr "Nikdo si se mnou nechce hrát ..."
msgid "I feel so alone ..."
msgstr "Cítím se tak osamělý ..."
msgid "Where's everybody?!"
msgstr "Kde jsou všichni?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Spím {secs} sekund ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Dobrou noc."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Čekání {secs} sekund ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Rozhlížím se ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hej {what} budeme kamarádi!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Asociuju se s {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Čus {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Rozhodl jsem se, že {mac} nepotřebuje žádnou WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Deautentikuju {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanuju {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Super, máme {num} nových handshaků!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Máš {count} nových zpráv!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ups, něco se pokazilo ... Restartuju ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Vykopnuto {num} klientů\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Mám {num} nových kamarádů\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Mám {num} handshaků\n"
msgid "Met 1 peer"
msgstr "Potkal jsem jednoho kámoše"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Potkal jsem {num} kámošů"
#, 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 ""
"Chytal jsem pakety po dobu {duration} a vykopnul jsem {deauthed} klientů! Taky jsem potkal "
"{associated} nových kamarádů a snědl {handshakes} handshaků! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "hodiny"
msgid "minutes"
msgstr "minuty"
msgid "seconds"
msgstr "sekundy"
msgid "hour"
msgstr "hodina"
msgid "minute"
msgstr "minuta"
msgid "second"
msgstr "sekunda"

View File

@@ -26,7 +26,7 @@ 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,7 +35,7 @@ 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."
@@ -83,7 +83,7 @@ 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ß!"
@@ -93,7 +93,7 @@ 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?"
@@ -198,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 ..."
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"
@@ -247,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

@@ -8,25 +8,26 @@ msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: 2019-10-09 21:07+0000\n"
"Last-Translator: diegopastor <dpastor29@alumnos.uaq.mx>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: spanish\n"
"PO-Revision-Date: 2020-08-25 23:06+0200\n"
"Last-Translator: Sergio Ruiz <serginator@gmail.com>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language-Team: \n"
"X-Generator: Poedit 2.4.1\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hola, soy Pwnagotchi! Empezando ..."
msgstr "¡Hola, soy Pwnagotchi! Empezando ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nuevo día, nueva cazería, nuevos pwns!"
msgstr "Nuevo día, nueva caceria, nuevos pwns!"
msgid "Hack the Planet!"
msgstr "Hackea el planeta!"
msgstr "¡Hackea el planeta!"
msgid "AI ready."
msgstr "IA lista."
@@ -36,51 +37,51 @@ msgstr "La red neuronal está lista."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Oye, el canal {channel} está libre! Tú AP lo agradecerá."
msgstr "¡Oye, el canal {channel} está libre! Tu AP lo agradecerá."
msgid "I'm bored ..."
msgstr "Estoy aburrido ..."
msgid "Let's go for a walk!"
msgstr "Vamos por un paseo!"
msgstr "¡Vamos por un paseo!"
msgid "This is the best day of my life!"
msgstr "Este es el mejor día de mi vida!"
msgstr "¡Este es el mejor día de mi vida!"
msgid "Shitty day :/"
msgstr "Día de mierda :/"
msgid "I'm extremely bored ..."
msgstr "Estoy extremadamente aburrido ..."
msgstr "Estoy muy aburrido ..."
msgid "I'm very sad ..."
msgstr "Estoy muy triste ..."
msgid "I'm sad"
msgstr "Estoy triste."
msgstr "Estoy triste"
msgid "I'm living the life!"
msgstr "Estoy viviendo la vida!"
msgstr "¡Estoy viviendo la vida!"
msgid "I pwn therefore I am."
msgstr "Pwneo, por lo tanto, existo"
msgstr "Pwneo, luego existo."
msgid "So many networks!!!"
msgstr "Cuantas redes!!!"
msgstr "¡¡¡Cuántas redes!!!"
msgid "I'm having so much fun!"
msgstr "Me estoy divirtiendo mucho!"
msgstr "¡Me estoy divirtiendo mucho!"
msgid "My crime is that of curiosity ..."
msgstr "Mi único crimen es la curiosidad ..."
msgstr "Mi crimen es la curiosidad ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Hola {name}! encantado de conocerte."
msgstr "¡Hola {name}! Encantado de conocerte. {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "La unidad {name} está cerca!"
msgstr "¡La unidad {name} está cerca! {name}"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
@@ -92,14 +93,14 @@ msgstr "{name} se fue ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Uy ... {name} se fue"
msgstr "Ups ... {name} se fue."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} perdido!"
msgstr "¡{name} perdido!"
msgid "Missed!"
msgstr "Perdido!"
msgstr "¡Perdido!"
msgid "Nobody wants to play with me ..."
msgstr "Nadie quiere jugar conmigo ..."
@@ -108,11 +109,11 @@ msgid "I feel so alone ..."
msgstr "Me siento tan solo ..."
msgid "Where's everybody?!"
msgstr "Dónde está todo el mundo?"
msgstr "¡¿Dónde está todo el mundo?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Tomándo una siesta por {secs}s ..."
msgstr "Descansando durante {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
@@ -133,23 +134,23 @@ msgstr "Esperando {secs}s .."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Mirando al rededor ({secs}s)"
msgstr "Mirando alrededor ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Oye {what} seamos amigos!"
msgstr "¡Oye {what} seamos amigos!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Asociando a {what}"
msgstr "Asociándome a {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Ey {what}!"
msgstr "¡Ey {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Acabo de decidir que {mac} no necesita WiFi!"
msgstr "¡Acabo de decidir que {mac} no necesita WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
@@ -157,14 +158,14 @@ msgstr "Desautenticando a {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Expulsando y banneando a {mac}!"
msgstr "¡Expulsando y baneando a {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
msgstr "¡Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salió mal ... Reiniciándo ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salió mal ... Reiniciando ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
@@ -172,27 +173,27 @@ msgstr "Expulsamos {num} estaciones\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Hicimos {num} nuevos amigos\n"
msgstr "Hice {num} nuevos amigos\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Obtuvimos {num} handshakes\n"
msgstr "Consegui {num} handshakes\n"
msgid "Met 1 peer"
msgstr "Conocí 1 igual"
msgstr "Conocí 1 colega"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Conocí {num} iguales"
msgstr "Conocí {num} colegas"
#, 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"
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog "
"#pwnlife #hacktheplanet #skynet"
msgstr ""
"He estado pwneando por {duration} y expulsé {deauthed} clientes! También conocí"
"{associated} nuevos amigos y me comí {handshakes} handshakes! #pwnagotchi "
"¡He estado pwneando por {duration} y expulsé {deauthed} clientes! También "
"conocí {associated} nuevos amigos y comí {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"

View File

@@ -72,13 +72,13 @@ msgstr "Je suis triste"
#, fuzzy
msgid "Leave me alone ..."
msgstr "Je me sens si seul..."
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."
@@ -114,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."
@@ -144,7 +144,7 @@ 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 "Zzzzz"
@@ -161,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!"
@@ -193,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
@@ -208,18 +208,18 @@ msgstr "{num} stations kick\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "A {num} nouveaux amis\n"
msgstr "A fait {num} nouve(l/aux) ami(s)\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "A {num} handshakes\n"
msgid "Met 1 peer"
msgstr "1 pair rencontré"
msgstr "1 camarade rencontré"
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num} pairs 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

@@ -20,7 +20,7 @@ msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hoi, Ik ben Pwnagotchi! Opstarten ..."
msgstr "Hoi, Ik ben Pwnagotchi! Aan het opstarten ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nieuwe dag, nieuwe jacht, nieuwe pwns!"
@@ -32,7 +32,7 @@ msgid "AI ready."
msgstr "AI is klaar."
msgid "The neural network is ready."
msgstr "Neuronen netwerkis klaar voor gebruik."
msgstr "Neuronen netwerk is klaar."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
@@ -42,37 +42,37 @@ msgid "I'm bored ..."
msgstr "Ik verveel me ..."
msgid "Let's go for a walk!"
msgstr "Laten we een rondje lopen!"
msgstr "Laten we gaan wandelen!"
msgid "This is the best day of my life!"
msgstr "Dit is de beste dag van mijn leven!"
msgid "Shitty day :/"
msgstr "Ruk dag :/"
msgstr "Wat een rotdag :/"
msgid "I'm extremely bored ..."
msgstr "Ik verveel me kapot ..."
msgid "I'm very sad ..."
msgstr "Ik ben ergverdrietig ..."
msgstr "Ik ben erg verdrietig ..."
msgid "I'm sad"
msgstr "Ik ben verdrietig"
msgid "I'm living the life!"
msgstr "Beter kan het levenniet worden!"
msgstr "Beter kan het leven niet worden!"
msgid "I pwn therefore I am."
msgstr "Ik pwn daarom besta ik."
msgstr "Ik pwn daarom ben ik er."
msgid "So many networks!!!"
msgstr "Zo veel netwerken!!!"
msgid "I'm having so much fun!"
msgstr "Dit is zo leuk!"
msgstr "Ik heb zoveel plezier!"
msgid "My crime is that of curiosity ..."
msgstr "Mijn enige misdrijf is mijn nieuwsgierigheid ..."
msgstr "Mijn misdrijf is mijn nieuwsgierigheid ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
@@ -88,11 +88,11 @@ msgstr "Uhm ...tot ziens {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} is weg"
msgstr "{name} is weg ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Whoopsie ...{name} is weg"
msgstr "Whoopsie ...{name} is weg."
#, python-brace-format
msgid "{name} missed!"
@@ -102,10 +102,10 @@ msgid "Missed!"
msgstr "Gemist!"
msgid "Nobody wants to play with me ..."
msgstr "Niemand wil metmij spelen ..."
msgstr "Niemand wil met mij spelen ..."
msgid "I feel so alone ..."
msgstr "Zo alleen ..."
msgstr "Ik voel me zo alleen ..."
msgid "Where's everybody?!"
msgstr "Waar is iedereen?!"
@@ -119,11 +119,11 @@ msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgstr "ZzzZzzz ({secs}s)"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Even {secs}s wachten ..."
msgstr "Ik wacht voor {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
@@ -131,7 +131,7 @@ msgstr "Rond kijken ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what}, laten we vriendenworden!"
msgstr "Hey {what}, laten we vrienden worden!"
#, python-brace-format
msgid "Associating to {what}"
@@ -139,26 +139,26 @@ msgstr "Verbinden met {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr ""
msgstr "Yo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Ik vind dat {mac} genoeg WiFiheeft gehad!"
msgstr "Ik besloot dat {mac} geen WiFi meer nodig heeft!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "De-autoriseren {mac}"
msgstr "Deauthenticatie van {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Ik ga {mac} even kicken!"
msgstr "Kickbanning {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Gaaf, we hebben {num} nieuwe handshake{plural}!"
msgstr "Cool, we hebben {num} nieuwe handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oops, iets ging fout ...Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, er ging iets fout ...Rebooting ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
@@ -166,11 +166,11 @@ msgstr "{num} stations gekicked\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num} nieuwe vrienden\n"
msgstr "{num} nieuwe vrienden gemaakt.\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num} nieuwe handshakes\n"
msgstr "Ik heb {num} nieuwe handshakes\n"
msgid "Met 1 peer"
msgstr "1 peer ontmoet"
@@ -190,19 +190,19 @@ msgstr ""
"gegeten! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgstr "uren"
msgid "minutes"
msgstr ""
msgstr "minuten"
msgid "seconds"
msgstr ""
msgstr "seconden"
msgid "hour"
msgstr ""
msgstr "uur"
msgid "minute"
msgstr ""
msgstr "minuut"
msgid "second"
msgstr ""
msgstr "seconde"

View File

@@ -198,7 +198,7 @@ msgstr "Fett, vi fikk {num} nye håndtrykk!"
msgid "You have {count} new message{plural}!"
msgstr "Du har {count} melding{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oi, noe gikk helt skakk ... Rebooter ..."
#, python-brace-format

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

View File

@@ -3,7 +3,7 @@
# 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"
@@ -199,7 +199,7 @@ msgstr "Șmecher, avem {num} de handshake-uri noi!"
msgid "You have {count} new message{plural}!"
msgstr "Ai {count} mesaj(e) nou/noi!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "OOps, ceva s-a întamplat... Îmi dau reboot...+"
#, python-brace-format

View File

@@ -5,17 +5,21 @@
# Second author <https://github.com/mbgroot>, 2019
msgid ""
msgstr ""
"Project-Id-Version: 0.0.2"
"Report-Msgid-Bugs-To: m-b-g@yandex.ru"
"POT-Creation-Date: 2019-11-27 16:47+0200"
"PO-Revision-Date: 2019-11-27 18:50+0300"
"Last-Translator: Evgeny Zelenin <m-b-g@yandex.ru>"
"Language-Team: ru"
"Language: ru"
"MIME-Version: 1.0\n"
"Project-Id-Version: Pwnagotchi Russian translation v 0.0.2\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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"
"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 "Хрррр..."
@@ -34,12 +38,14 @@ 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 "Чтение логов последнего сеанса..."
@@ -67,6 +73,7 @@ msgstr "Мне очень грустно …"
msgid "I'm sad"
msgstr "Мне грустно"
msgid "Leave me alone ..."
msgstr "Оставь меня в покое..."
@@ -95,9 +102,10 @@ msgstr "Привет, {name}! Рад встрече с тобой!"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Цель {name} близко!"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Хэй {nume}! Как дела?"
msgstr "Хэй {name}! Как дела?"
#, python-brace-format
msgid "Unit {name} is nearby!"
@@ -121,6 +129,7 @@ msgstr "{name} упустил!"
msgid "Missed!"
msgstr "Промахнулся!"
msgid "Good friends are a blessing!"
msgstr "Хорошие друзья - это благословение!"
@@ -146,6 +155,7 @@ msgstr "Хррр..."
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "Хррррр.. ({secs}c)"
msgid "Good night."
msgstr "Доброй ночи."
@@ -188,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

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

View File

@@ -177,7 +177,7 @@ msgstr "Super, máme {num} nový handshake{plural}!"
msgid "You have {count} new message{plural}!"
msgstr "Máte {count} novú správu{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Ops, niečo sa pokazilo ... Reštartujem sa ..."
#, python-brace-format

Binary file not shown.

View File

@@ -198,7 +198,7 @@ msgstr "Bien, obtuvimos {num} nuevos handshake{plural}!"
msgid "You have {count} new message{plural}!"
msgstr "Tienes {count} nuevos mensajes{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salio mal ... Reiniciando ..."
#, python-brace-format

Binary file not shown.

View File

@@ -0,0 +1,227 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <shark_shaking@hotmail.com>, 2020.
# 有很多翻譯不到味,如果有繁體愛好者,歡迎之後大家一起翻譯!
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-10-21 15:56+0200\n"
"PO-Revision-Date: 2020-10-22 10:00+0008\n"
"Last-Translator: ShaqKSmith <shark_shaking@hotmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: traditional chinese\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 "HI!我是Pwnagotchi!\n程式啟動..."
msgid "New day, new hunt, new pwns!"
msgstr "新的一天!\n新的狩獵!新的入侵!"
msgid "Hack the Planet!"
msgstr "我要駭入\n地球的所有人!"
msgid "AI ready."
msgstr "人工智慧已啟動."
msgid "The neural network is ready."
msgstr "神經網路已啟動."
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}很順暢!\n你的WIFI會感謝你的."
msgid "I'm bored ..."
msgstr "我好無聊..."
msgid "Let's go for a walk!"
msgstr "我們出去走走吧!"
msgid "This is the best day of my life!"
msgstr "這是我生命中最美好的一天!"
msgid "Shitty day :/"
msgstr "糟糕的一天 :/"
msgid "I'm extremely bored ..."
msgstr "我超無聊的..."
msgid "I'm very sad ..."
msgstr "我好難過..."
msgid "I'm sad"
msgstr "傷心。"
msgid "I'm living the life!"
msgstr "真是充實的一生!"
msgid "I pwn therefore I am."
msgstr "我駭故我在."
msgid "So many networks!!!"
msgstr "好多網路啊!!!"
msgid "I'm having so much fun!"
msgstr "我玩的超級開心!"
msgid "My crime is that of curiosity ..."
msgstr "我的缺點就是\n太好奇了..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you."
msgstr "Hello{name}!\n很高興認識你."
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "{name}\n就在附近!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "啊 ... \n拜拜{name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name}\n不見了 ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "歐哦...\n{name}\n不見了."
#, python-brace-format
msgid "{name} missed!"
msgstr "我剛剛錯過了{name}!"
msgid "Missed!"
msgstr "又錯過了!"
msgid "Good friends are a blessing!"
msgstr "有個好朋友\n真幸福!"
msgid "I love my friends!"
msgstr "我喜歡\n我的朋友!"
msgid "Nobody wants to play with me ..."
msgstr "沒人想跟我玩..."
msgid "I feel so alone ..."
msgstr "我感覺好孤單..."
msgid "Where's everybody?!"
msgstr "大家都去哪裡了?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "我想瞇{secs}秒一下..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz({secs}秒)"
msgid "Good night."
msgstr "晚安."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "等我{secs}秒..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "環顧四周({secs}秒)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "嗨\n{what}\n讓我我們來當朋友吧!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "正在連接\n{what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "喲,\n{what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "我要讓\n{mac}\n斷線!\n他不需要上網!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "解除\n{mac}\n的授權中"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "把\n{mac}\n踢出中!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "酷哦,我們抓到{num}個\n新的握手包{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "你有{count}個新訊息{plural}!"
msgid "Oops, something went wrong ... Rebooting ..."
msgstr "喔歐,有些地方出錯了...\n重新啟動中..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "踢了 {num} 個設備\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "交了 {num} 個新朋友\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "捕獲了 {num} 個握手包\n"
msgid "Met 1 peer 同好"
msgstr "遇到了 個"
#, python-brace-format
msgid "Met {num} peers"
msgstr "遇到了 {num} 個同好"
#, 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 "我花了{duration}的時間\n駭入和踢了{deauthed}好多設備.\n"
"我還交了好多{associated}新朋友,\n而且抓到了{handshakes}握手包!"
"#pwnagotchi#入侵日志 #駭客人生 #入侵整個星球 #天網"
msgid "hours"
msgstr "時"
msgid "minutes"
msgstr "分"
msgid "seconds"
msgstr "秒"
msgid "hour"
msgstr "時"
msgid "minute"
msgstr "分"
msgid "second"
msgstr "秒"

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

@@ -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
@@ -244,3 +244,7 @@ msgstr ""
msgid "second"
msgstr ""
#, python-brace-format
msgid "Uploading data to {to} ..."
msgstr ""

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
@@ -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

@@ -4,7 +4,8 @@ import _thread
import threading
import importlib, importlib.util
import logging
from pwnagotchi.ui import view
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
loaded = {}
@@ -36,16 +37,32 @@ def toggle_plugin(name, enable=True):
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:
if not name in pwnagotchi.config['main']['plugins']:
pwnagotchi.config['main']['plugins'][name] = dict()
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])
if name in loaded and pwnagotchi.config and name in pwnagotchi.config['main']['plugins']:
loaded[name].options = pwnagotchi.config['main']['plugins'][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
@@ -54,12 +71,16 @@ def toggle_plugin(name, enable=True):
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)
@@ -123,3 +144,4 @@ def load(config):
plugin.options = config['main']['plugins'][name]
on('loaded')
on('config_changed', config)

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

@@ -0,0 +1,394 @@
# Handles the commandline stuff
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
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(config)
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(config):
"""
Updates the database
"""
global SAVE_DIR
urls = config['main']['custom_plugin_repos']
if not urls:
logging.info('No plugin repositories configured.')
return 1
rc = 0
for idx, REPO_URL in enumerate(urls):
DEST = os.path.join(SAVE_DIR, 'plugins%d.zip' % idx)
logging.info('Downloading plugins from %s to %s', REPO_URL, 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)
except Exception as ex:
logging.error('Error while updating plugins: %s', ex)
rc = 1
return rc

View File

@@ -6,12 +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):
@@ -30,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'])
@@ -154,13 +153,16 @@ class AutoUpdate(plugins.Plugin):
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):
if self.lock.locked():
return
with self.lock:
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)
@@ -183,7 +185,7 @@ class AutoUpdate(plugins.Plugin):
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')
('evilsocket/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
]
for repo, local_version, is_native, svc_name in to_check:

View File

@@ -419,15 +419,19 @@ class Device:
class BTTether(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__version__ = '1.1.0'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
def __init__(self):
self.ready = False
self.options = dict()
self.devices = dict()
self.lock = Lock()
self.running = True
self.status = '-'
def on_loaded(self):
# new config
@@ -437,7 +441,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
@@ -448,8 +452,8 @@ 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):
logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
if opt not in self.options or self.options[opt] is None:
logging.error("BT-TETHER: Please specify the %s in your config.toml.", opt)
return
self.devices['legacy'] = Device(name='legacy', **self.options)
@@ -466,22 +470,10 @@ class BTTether(plugins.Plugin):
return
logging.info("BT-TETHER: Successfully loaded ...")
self.ready = True
def on_unload(self, ui):
with ui._lock:
ui.remove_element('bluetooth')
while self.running:
time.sleep(1)
def on_ui_setup(self, ui):
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
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
@@ -508,11 +500,11 @@ class BTTether(plugins.Plugin):
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')
self.status = 'NF'
continue
except Exception as bt_ex:
logging.error(bt_ex)
ui.set('bluetooth', 'NF')
self.status = 'NF'
continue
paired = bt.is_paired()
@@ -521,7 +513,7 @@ class BTTether(plugins.Plugin):
logging.debug('BT-TETHER: Paired with %s.', device.name)
else:
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
ui.set('bluetooth', 'PE')
self.status = 'PE'
continue
else:
logging.debug('BT-TETHER: Already paired.')
@@ -539,17 +531,17 @@ class BTTether(plugins.Plugin):
continue
if interface is None:
ui.set('bluetooth', 'BE')
self.status = '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')
self.status = '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')
self.status = 'NF'
continue
addr = f"{device.ip}/{device.netmask}"
@@ -561,7 +553,7 @@ class BTTether(plugins.Plugin):
wrapped_interface = IfaceWrapper(interface)
logging.debug('BT-TETHER: Add ip to %s', interface)
if not wrapped_interface.set_addr(addr):
ui.set('bluetooth', 'AE')
self.status = 'AE'
logging.debug("BT-TETHER: Could not add ip to %s", interface)
continue
@@ -580,4 +572,20 @@ class BTTether(plugins.Plugin):
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if any_device_connected:
ui.set('bluetooth', 'C')
self.status = 'C'
def on_unload(self, ui):
self.running = False
with ui._lock:
ui.remove_element('bluetooth')
def on_ui_setup(self, ui):
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):
ui.set('bluetooth', self.status)

View File

@@ -10,10 +10,13 @@ from pwnagotchi.ui.view import BLACK
class GPS(plugins.Plugin):
__author__ = "evilsocket@gmail.com"
__version__ = "1.0.0"
__version__ = "1.0.1"
__license__ = "GPL3"
__description__ = "Save GPS coordinates whenever an handshake is captured."
LINE_SPACING = 10
LABEL_SPACING = 0
def __init__(self):
self.running = False
self.coordinates = None
@@ -44,37 +47,63 @@ class GPS(plugins.Plugin):
self.coordinates = info["gps"]
gps_filename = filename.replace(".pcap", ".gps.json")
logging.info(f"saving GPS to {gps_filename} ({self.coordinates})")
with open(gps_filename, "w+t") as fp:
json.dump(self.coordinates, 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():
# guessed values, add tested ones if you can
lat_pos = (112, 30)
lon_pos = (112, 49)
alt_pos = (87, 63)
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)
try:
# Configure line_spacing
line_spacing = int(self.options['linespacing'])
except Exception:
# Set default value
line_spacing = self.LINE_SPACING
label_spacing = 0
try:
# Configure position
pos = self.options['position'].split(',')
pos = [int(x.strip()) for x in pos]
lat_pos = (pos[0] + 5, pos[1])
lon_pos = (pos[0], pos[1] + line_spacing)
alt_pos = (pos[0] + 5, pos[1] + (2 * line_spacing))
except Exception:
# Set default value based on display type
if ui.is_waveshare_v2():
lat_pos = (127, 74)
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 = (122, 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)
elif ui.is_dfrobot_v2():
lat_pos = (127, 74)
lon_pos = (122, 84)
alt_pos = (127, 94)
elif ui.is_waveshare27inch():
lat_pos = (6, 120)
lon_pos = (1, 135)
alt_pos = (6, 150)
else:
# guessed values, add tested ones if you can
lat_pos = (127, 51)
lon_pos = (122, 61)
alt_pos = (127, 71)
ui.add_element(
"latitude",
@@ -85,7 +114,7 @@ class GPS(plugins.Plugin):
position=lat_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
label_spacing=self.LABEL_SPACING,
),
)
ui.add_element(
@@ -97,7 +126,7 @@ class GPS(plugins.Plugin):
position=lon_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
label_spacing=self.LABEL_SPACING,
),
)
ui.add_element(
@@ -109,11 +138,10 @@ class GPS(plugins.Plugin):
position=alt_pos,
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=label_spacing,
label_spacing=self.LABEL_SPACING,
),
)
def on_unload(self, ui):
with ui._lock:
ui.remove_element('latitude')
@@ -128,5 +156,5 @@ class GPS(plugins.Plugin):
# 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 ")
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']:
@@ -122,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,273 @@
import os
import logging
import threading
from itertools import islice
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 table = document.getElementById('table');
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.display = "none";
}
table.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 tr, tds, td, i, txtValue;
filterVal = filter.value.toUpperCase();
tr = table.getElementsByTagName("tr");
for (i = 1; i < tr.length; i++) {
txtValue = tr[i].textContent || tr[i].innerText;
if (txtValue.toUpperCase().indexOf(filterVal) > -1) {
tr[i].style.display = "table-row";
} else {
tr[i].style.display = "none";
}
}
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="table">
<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 ''.join(f.readlines()[-self.options.get('max-lines', 4096):])
while True:
yield f.readline()
return Response(generate(), mimetype='text/plain')
abort(404)

View File

@@ -1,6 +1,6 @@
# memtemp shows memory infos and cpu temperature
#
# mem usage, cpu load, cpu temp
# mem usage, cpu load, cpu temp, cpu frequency
#
###############################################################
#
@@ -16,8 +16,15 @@
# - Added CPU load
# - Added horizontal and vertical orientation
#
# 19-09-2020 by crahan <crahan@n00.be>
# - Added CPU frequency
# - Made field types and order configurable (max 3 fields)
# - Made line spacing and position configurable
# - Updated code to dynamically generate UI elements
# - Changed horizontal UI elements to Text
# - Updated to version 1.0.2
###############################################################
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.components import LabeledValue, Text
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
@@ -27,54 +34,31 @@ import logging
class MemTemp(plugins.Plugin):
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__version__ = '1.0.2'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
ALLOWED_FIELDS = {
'mem': 'mem_usage',
'cpu': 'cpu_load',
'temp': 'cpu_temp',
'freq': 'cpu_freq'
}
DEFAULT_FIELDS = ['mem', 'cpu', 'temp']
LINE_SPACING = 10
LABEL_SPACING = 0
FIELD_WIDTH = 4
def on_loaded(self):
logging.info("memtemp plugin loaded.")
def mem_usage(self):
return int(pwnagotchi.mem_usage() * 100)
return f"{int(pwnagotchi.mem_usage() * 100)}%"
def cpu_load(self):
return int(pwnagotchi.cpu_load() * 100)
return f"{int(pwnagotchi.cpu_load() * 100)}%"
def on_ui_setup(self, ui):
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'] == "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):
def cpu_temp(self):
if self.options['scale'] == "fahrenheit":
temp = (pwnagotchi.temperature() * 9 / 5) + 32
symbol = "f"
@@ -85,11 +69,116 @@ class MemTemp(plugins.Plugin):
# default to celsius
temp = pwnagotchi.temperature()
symbol = "c"
return f"{temp}{symbol}"
def cpu_freq(self):
with open('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq', 'rt') as fp:
return f"{round(float(fp.readline())/1000000, 1)}G"
def pad_text(self, data):
return " " * (self.FIELD_WIDTH - len(data)) + data
def on_ui_setup(self, ui):
try:
# Configure field list
self.fields = self.options['fields'].split(',')
self.fields = [x.strip() for x in self.fields if x.strip() in self.ALLOWED_FIELDS.keys()]
self.fields = self.fields[:3] # limit to the first 3 fields
except Exception:
# Set default value
self.fields = self.DEFAULT_FIELDS
try:
# Configure line_spacing
line_spacing = int(self.options['linespacing'])
except Exception:
# Set default value
line_spacing = self.LINE_SPACING
try:
# Configure position
pos = self.options['position'].split(',')
pos = [int(x.strip()) for x in pos]
if self.options['orientation'] == "vertical":
v_pos = (pos[0], pos[1])
else:
h_pos = (pos[0], pos[1])
except Exception:
# Set default position based on screen type
if ui.is_waveshare_v2():
h_pos = (178, 84)
v_pos = (197, 74)
elif ui.is_waveshare_v1():
h_pos = (170, 80)
v_pos = (165, 61)
elif ui.is_waveshare144lcd():
h_pos = (53, 77)
v_pos = (73, 67)
elif ui.is_inky():
h_pos = (140, 68)
v_pos = (160, 54)
elif ui.is_waveshare27inch():
h_pos = (192, 138)
v_pos = (211, 122)
else:
h_pos = (155, 76)
v_pos = (175, 61)
if self.options['orientation'] == "vertical":
ui.set('memtemp',
" mem:%s%%\n cpu:%s%%\ntemp:%s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
# Dynamically create the required LabeledValue objects
for idx, field in enumerate(self.fields):
v_pos_x = v_pos[0]
v_pos_y = v_pos[1] + ((len(self.fields) - 3) * -1 * line_spacing)
ui.add_element(
f"memtemp_{field}",
LabeledValue(
color=BLACK,
label=f"{self.pad_text(field)}:",
value="-",
position=(v_pos_x, v_pos_y + (idx * line_spacing)),
label_font=fonts.Small,
text_font=fonts.Small,
label_spacing=self.LABEL_SPACING,
)
)
else:
# default to horizontal
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %s%s" % (self.mem_usage(), self.cpu_load(), temp, symbol))
h_pos_x = h_pos[0] + ((len(self.fields) - 3) * -1 * 25)
h_pos_y = h_pos[1]
ui.add_element(
'memtemp_header',
Text(
color=BLACK,
value=" ".join([self.pad_text(x) for x in self.fields]),
position=(h_pos_x, h_pos_y),
font=fonts.Small,
)
)
ui.add_element(
'memtemp_data',
Text(
color=BLACK,
value=" ".join([self.pad_text("-") for x in self.fields]),
position=(h_pos_x, h_pos_y + line_spacing),
font=fonts.Small,
)
)
def on_unload(self, ui):
with ui._lock:
if self.options['orientation'] == "vertical":
for idx, field in enumerate(self.fields):
ui.remove_element(f"memtemp_{field}")
else:
# default to horizontal
ui.remove_element('memtemp_header')
ui.remove_element('memtemp_data')
def on_ui_update(self, ui):
if self.options['orientation'] == "vertical":
for idx, field in enumerate(self.fields):
ui.set(f"memtemp_{field}", getattr(self, self.ALLOWED_FIELDS[field])())
else:
# default to horizontal
data = " ".join([self.pad_text(getattr(self, self.ALLOWED_FIELDS[x])()) for x in self.fields])
ui.set('memtemp_data', data)

View File

@@ -7,18 +7,18 @@ 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.2'
__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()
@@ -26,12 +26,14 @@ class NetPos(plugins.Plugin):
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()
@@ -47,6 +49,8 @@ class NetPos(plugins.Plugin):
saved_file.write(x + "\n")
def on_internet_available(self, agent):
if self.lock.locked():
return
with self.lock:
if self.ready:
config = agent.config()
@@ -124,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,16 +1,18 @@
import os
import csv
import logging
import re
import requests
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile
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.1'
__version__ = '2.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
@@ -18,7 +20,7 @@ class OnlineHashCrack(plugins.Plugin):
self.ready = False
try:
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
except JSONDecodeError as json_err:
except JSONDecodeError:
os.remove('/root/.ohc_uploads')
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
@@ -28,29 +30,16 @@ class OnlineHashCrack(plugins.Plugin):
"""
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):
"""
@@ -66,49 +55,98 @@ class OnlineHashCrack(plugins.Plugin):
files=payload,
timeout=timeout)
if 'already been sent' in result.text:
logging.warning(f"{path} was already uploaded.")
logging.debug(f"{path} was already uploaded.")
except requests.exceptions.RequestException as e:
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
logging.debug(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_webhook(self, path, request):
import requests
from flask import redirect
s = requests.Session()
s.get('https://www.onlinehashcrack.com/dashboard')
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
return redirect(r.url, code=302)
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
"""
if not self.ready or self.lock.locked():
return
with self.lock:
if self.ready:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
# pull out whitelisted APs
handshake_paths = remove_whitelisted(handshake_paths, self.options['whitelist'])
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.on_uploading(f"onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
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')]
try:
self._upload_to_ohc(handshake)
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.debug(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.debug("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.debug("OHC: %s", os_e)
continue
# pull out whitelisted APs
handshake_paths = filter(lambda path: self._filter_handshake_file(path), handshake_paths)
display.on_normal()
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._upload_to_ohc(handshake)
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
continue
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

@@ -18,7 +18,7 @@ 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):

View File

@@ -1,8 +1,10 @@
import os
import logging
import threading
from datetime import datetime
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
@@ -14,11 +16,22 @@ TEMPLATE = """
{% 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>
@@ -31,94 +44,137 @@ TEMPLATE = """
{% 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 loadData(url, elm, title) {
var data = ajaxDataRenderer(url);
var plot_os = $.jqplot(elm, data.values,{
title: title,
stackSeries: true,
seriesDefaults: {
showMarker: false,
fill: true,
fillAndStroke: true
},
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:{
min: 0,
tickOptions:{formatString:'%.2f'}
}
},
highlighter: {
show: true,
sizeAdjust: 7.5
},
cursor:{
show: true,
tooltipLocation:'sw'
var ajaxDataRenderer = function(url, plot, options) {
var ret = null;
$.ajax({
async: false,
url: url,
dataType:"json",
success: function(data) {
ret = data;
}
}).replot({
axes:{
xaxis:{
renderer:$.jqplot.DateAxisRenderer,
tickOptions:{formatString:'%H:%M:%S'}
},
yaxis:{
min: 0,
tickOptions:{formatString:'%.2f'}
}
});
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 loadAll() {
loadData('/plugins/session-stats/os', 'chart_os', 'OS')
loadData('/plugins/session-stats/temp', 'chart_temp', 'Temp')
loadData('/plugins/session-stats/nums', 'chart_nums', 'Wifi')
loadData('/plugins/session-stats/duration', 'chart_duration', 'Sleeping')
loadData('/plugins/session-stats/epoch', 'chart_epoch', 'Epochs')
}
function loadSessionFiles() {
loadFiles('/plugins/session-stats/session', 'session');
$("#session").change(function() {
loadSessionData();
});
}
loadAll();
setInterval(loadAll, 60000);
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 %}
<div id="chart_os" style="height:400px;width:100%; "></div>
<div id="chart_temp" style="height:400px;width:100%; "></div>
<div id="chart_nums" style="height:400px;width:100%; "></div>
<div id="chart_duration" style="height:400px;width:100%; "></div>
<div id="chart_epoch" style="height:400px;width:100%; "></div>
<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'
@@ -126,27 +182,31 @@ class SessionStats(plugins.Plugin):
__description__ = 'This plugin displays stats of the current session.'
def __init__(self):
self.ready = False
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.")
self.ready = True
def on_unloaded(self, ui):
pass
def on_epoch(self, agent, epoch, epoch_data):
"""
Save the epoch_data to self.stats
"""
with self.lock:
self.stats[datetime.now().strftime("%H:%M:%S")] = epoch_data
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):
@@ -162,11 +222,13 @@ class SessionStats(plugins.Plugin):
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 == "nums":
elif path == "wifi":
extract_keys = [
'missed_interactions',
'num_hops',
@@ -182,14 +244,20 @@ class SessionStats(plugins.Plugin):
'duration_secs',
'slept_for_secs',
]
elif path == "reward":
extract_keys = [
'reward',
]
elif path == "epoch":
extract_keys = [
'blind_for_epochs',
'inactive_for_epochs',
'active_for_epochs',
]
elif path == "session":
return jsonify({'files': os.listdir(self.options['save_directory'])})
with self.lock:
return jsonify(SessionStats.extract_key_values(self.stats, extract_keys))
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

@@ -7,12 +7,18 @@
# 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
#
# To display external power supply status you need to bridge the necessary pins on the UPS-Lite board. See instructions in the UPS-Lite repo.
import logging
import struct
import RPi.GPIO as GPIO
import pwnagotchi
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
import pwnagotchi.plugins as plugins
# TODO: add enable switch in config.yml an cleanup all to the best place
@@ -41,6 +47,14 @@ class UPS:
except:
return 0.0
def charging(self):
try:
GPIO.setmode(GPIO.BCM)
GPIO.setup(4, GPIO.IN)
return '+' if GPIO.input(4) == GPIO.HIGH else '-'
except:
return '-'
class UPSLite(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
@@ -63,4 +77,10 @@ class UPSLite(plugins.Plugin):
ui.remove_element('ups')
def on_ui_update(self, ui):
ui.set('ups', "%2i%%" % self.ups.capacity())
capacity = self.ups.capacity()
charging = self.ups.charging()
ui.set('ups', "%2i%s" % (capacity, charging))
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,36 @@
import os
import logging
import re
import subprocess
from io import TextIOWrapper
from pwnagotchi import plugins
class Watchdog(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '0.1.0'
__license__ = 'GPL3'
__description__ = 'Restart pwnagotchi when blindbug is detected.'
def __init__(self):
self.options = dict()
self.pattern = re.compile(r'brcmf_cfg80211_nexmon_set_channel.*?Set Channel failed')
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
logging.info("Watchdog plugin loaded.")
def on_epoch(self, agent, epoch, epoch_data):
# get last 10 lines
last_lines = ''.join(list(TextIOWrapper(subprocess.Popen(['journalctl','-n10','-k', '--since', '-5m'],
stdout=subprocess.PIPE).stdout))[-10:])
if len(self.pattern.findall(last_lines)) >= 5:
display = agent.view()
display.set('status', 'Blind-Bug detected. Restarting.')
display.update(force=True)
logging.info('[WATCHDOG] Blind-Bug detected. Restarting.')
mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
import pwnagotchi
pwnagotchi.reboot(mode=mode)

View File

@@ -1,182 +1,190 @@
import logging
import json
import yaml
import toml
import _thread
import pwnagotchi.plugins as plugins
from pwnagotchi import restart
from pwnagotchi import restart, plugins
from pwnagotchi.utils import save_config
from flask import abort
from flask import render_template_string
INDEX = """
<html>
<head>
<meta name="viewport" content="width=device-width, user-scalable=0" />
<title>
webcfg
</title>
<style>
#divTop {
position: -webkit-sticky;
position: sticky;
top: 0px;
width: 100%;
font-size: 16px;
padding: 5px;
border: 1px solid #ddd;
margin-bottom: 5px;
}
{% extends "base.html" %}
{% set active_page = "plugins" %}
{% block title %}
Webcfg
{% endblock %}
#searchText {
width: 100%;
}
{% block meta %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=0" />
{% endblock %}
table {
table-layout: auto;
width: 100%;
}
{% 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;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
#searchText {
width: 100%;
}
th, td {
padding: 15px;
text-align: left;
}
table {
table-layout: auto;
width: 100%;
}
table tr:nth-child(even) {
background-color: #eee;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
table tr:nth-child(odd) {
background-color: #fff;
}
th, td {
padding: 15px;
text-align: left;
}
table th {
background-color: black;
color: white;
}
table tr:nth-child(even) {
background-color: #eee;
}
.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;
}
table tr:nth-child(odd) {
background-color: #fff;
}
.remove:hover {
background-color: white;
color: black;
}
table th {
background-color: black;
color: white;
}
#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;
}
.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;
}
#divTop {
display: table;
width: 100%;
}
#divTop > * {
display: table-cell;
}
#divTop > span {
width: 1%;
}
#divTop > input {
width: 100%;
}
.remove:hover {
background-color: white;
color: black;
}
@media screen and (max-width:700px) {
table, tr, td {
padding:0;
border:1px solid 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;
}
table {
border:none;
}
#divTop {
display: table;
width: 100%;
}
#divTop > * {
display: table-cell;
}
#divTop > span {
width: 1%;
}
#divTop > input {
width: 100%;
}
tr:first-child, thead, th {
display:none;
border:none;
}
@media screen and (max-width:700px) {
table, tr, td {
padding:0;
border:1px solid black;
}
tr {
float: left;
width: 100%;
margin-bottom: 2em;
}
table {
border:none;
}
table tr:nth-child(odd) {
background-color: #eee;
}
tr:first-child, thead, th {
display:none;
border:none;
}
td {
float: left;
width: 100%;
padding:1em;
}
tr {
float: left;
width: 100%;
margin-bottom: 2em;
}
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;
}
table tr:nth-child(odd) {
background-color: #eee;
}
.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>
</head>
<body>
<div id="divTop">
<input type="text" id="searchText" onkeyup="filterTable()" 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>
<div id="content"></div>
<button id="btnSave" type="button" onclick="saveConfig()">Save</button>
</body>
<script type="text/javascript">
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");
@@ -232,11 +240,10 @@ INDEX = """
});
}
}
function filterTable(){
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("searchText");
filter = input.value.toUpperCase();
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");
@@ -447,8 +454,7 @@ INDEX = """
divContent.innerHTML = "";
divContent.appendChild(table);
});
</script>
</html>
{% endblock %}
"""
def serializer(obj):
@@ -464,16 +470,17 @@ class WebConfig(plugins.Plugin):
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.config = agent.config()
self.mode = "MANU" if agent.mode == "manual" else "AUTO"
self.ready = True
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_internet_available(self, agent):
self.config = agent.config()
self.mode = "MANU" if agent.mode == "manual" else "AUTO"
self.ready = True
self.mode = 'MANU' if agent.mode == 'manual' else 'AUTO'
def on_loaded(self):
"""
@@ -500,13 +507,10 @@ class WebConfig(plugins.Plugin):
elif request.method == "POST":
if path == "save-config":
try:
parsed_yaml = yaml.safe_load(str(request.get_json()))
with open('/etc/pwnagotchi/config.yml', 'w') as config_file:
yaml.safe_dump(parsed_yaml, config_file, encoding='utf-8',
allow_unicode=True, default_flow_style=False)
save_config(request.get_json(), '/etc/pwnagotchi/config.toml') # test
_thread.start_new_thread(restart, (self.mode,))
return "success"
except yaml.YAMLError as yaml_ex:
return "config error"
except Exception as ex:
logging.error(ex)
return "config error", 500
abort(404)

View File

@@ -3,10 +3,10 @@
<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="http://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.css" />
<link rel="stylesheet" type="text/css" href="http://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.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='http://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.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;}
@@ -141,7 +141,7 @@
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
opacity:0.8,
// maxZoom: 19
maxZoom: 20
});
var mymap = L.map('mapdiv');
@@ -270,5 +270,18 @@
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>

View File

@@ -6,23 +6,25 @@ import re
import datetime
from flask import Response
from functools import lru_cache
from dateutil.parser import parse
'''
2do:
- make+test the cache handling multiple clients
- cleanup the javascript in a class and handle "/newest" additions
- create map filters (only cracked APs, only last xx days, between 2 days with slider)
http://www.gistechsolutions.com/leaflet/DEMO/filter/filter.html
https://gis.stackexchange.com/questions/312737/filtering-interactive-leaflet-map-with-dropdown-menu
https://blogs.kent.ac.uk/websolutions/2015/01/29/filtering-map-markers-with-leaflet-js-a-brief-technical-overview/
http://www.digital-geography.com/filter-leaflet-maps-slider/
http://bl.ocks.org/zross/47760925fcb1643b4225
-
webgpsmap shows existing position data stored in your /handshakes/ directory
the plugin does the following:
- search for *.pcap files in your /handshakes/ dir
- for every found .pcap file it looks for a .geo.json or .gps.json or .paw-gps.json file with
latitude+longitude data inside and shows this position on the map
- if also an .cracked file with a plaintext password inside exist, it reads the content and shows the
position as green instead of red and the password inside the infopox of the position
special:
you can save the html-map as one file for offline use or host on your own webspace with "/plugins/webgpsmap/offlinemap"
'''
class Webgpsmap(plugins.Plugin):
__author__ = 'https://github.com/xenDE and https://github.com/dadav'
__version__ = '1.3.0'
__version__ = '1.4.0'
__name__ = 'webgpsmap'
__license__ = 'GPL3'
__description__ = 'a plugin for pwnagotchi that shows a openstreetmap with positions of ap-handshakes in your webbrowser'
@@ -33,8 +35,8 @@ class Webgpsmap(plugins.Plugin):
def __init__(self):
self.ready = False
def on_ready(self, agent):
self.config = agent.config()
def on_config_changed(self, config):
self.config = config
self.ready = True
def on_loaded(self):
@@ -49,6 +51,7 @@ class Webgpsmap(plugins.Plugin):
"""
# defaults:
response_header_contenttype = None
response_header_contentdisposition = None
response_mimetype = "application/xhtml+xml"
if not self.ready:
try:
@@ -63,7 +66,7 @@ class Webgpsmap(plugins.Plugin):
response_mimetype = "application/xhtml+xml"
response_header_contenttype = 'text/html'
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
logging.error(f"[webgpsmap] on_webhook NOT_READY error: {error}")
return
else:
if request.method == "GET":
@@ -73,7 +76,7 @@ class Webgpsmap(plugins.Plugin):
try:
response_data = bytes(self.get_html(), "utf-8")
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
logging.error(f"[webgpsmap] on_webhook / error: {error}")
return
response_status = 200
response_mimetype = "application/xhtml+xml"
@@ -87,7 +90,22 @@ class Webgpsmap(plugins.Plugin):
response_mimetype = "application/json"
response_header_contenttype = 'application/json'
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
logging.error(f"[webgpsmap] on_webhook all error: {error}")
return
elif path.startswith('offlinemap'):
# for download an all-in-one html file with positions.json inside
try:
self.ALREADY_SENT = list()
json_data = json.dumps(self.load_gps_from_dir(self.config['bettercap']['handshakes']))
html_data = self.get_html()
html_data = html_data.replace('var positions = [];', 'var positions = ' + json_data + ';positionsLoaded=true;drawPositions();')
response_data = bytes(html_data, "utf-8")
response_status = 200
response_mimetype = "application/xhtml+xml"
response_header_contenttype = 'text/html'
response_header_contentdisposition = 'attachment; filename=webgpsmap.html';
except Exception as error:
logging.error(f"[webgpsmap] on_webhook offlinemap: error: {error}")
return
# elif path.startswith('/newest'):
# # returns all positions newer then timestamp
@@ -119,9 +137,11 @@ class Webgpsmap(plugins.Plugin):
r = Response(response=response_data, status=response_status, mimetype=response_mimetype)
if response_header_contenttype is not None:
r.headers["Content-Type"] = response_header_contenttype
if response_header_contentdisposition is not None:
r.headers["Content-Disposition"] = response_header_contentdisposition
return r
except Exception as error:
logging.error(f"[webgpsmap] error: {error}")
logging.error(f"[webgpsmap] on_webhook CREATING_RESPONSE error: {error}")
return
# cache 2048 items
@@ -210,22 +230,22 @@ class Webgpsmap(plugins.Plugin):
}
# get ap password if exist
check_for = os.path.basename(pos_file[:-9]) + ".pcap.cracked"
check_for = os.path.basename(pos_file).split(".")[0] + ".pcap.cracked"
if check_for in all_files:
gps_data[ssid + "_" + mac]["pass"] = pos.password()
self.ALREADY_SENT += pos_file
except json.JSONDecodeError as error:
self.SKIP += pos_file
logging.error(f"[webgpsmap] JSONDecodeError in: {error}")
logging.error(f"[webgpsmap] JSONDecodeError in: {pos_file} - error: {error}")
continue
except ValueError as error:
self.SKIP += pos_file
logging.error(f"[webgpsmap] ValueError: {error}")
logging.error(f"[webgpsmap] ValueError: {pos_file} - error: {error}")
continue
except OSError as error:
self.SKIP += pos_file
logging.error(f"[webgpsmap] OSError: {error}")
logging.error(f"[webgpsmap] OSError: {pos_file} - error: {error}")
continue
logging.info(f"[webgpsmap] loaded {len(gps_data)} positions")
return gps_data
@@ -303,18 +323,7 @@ class PositionFile:
return_ts = self._json['ts']
elif 'Updated' in self._json:
# convert gps datetime to unix timestamp: "2019-10-05T23:12:40.422996+01:00"
date_iso_formated = self._json['Updated']
#fix timezone "Z": "2019-11-28T04:44:46.79231Z" >> "2019-11-28T04:44:46.79231+00:00"
if date_iso_formated.endswith("Z"):
date_iso_formated = date_iso_formated[:-1] + "+00:00"
# bad microseconds fix: fill/cut microseconds to 6 numbers
part1, part2, part3 = re.split('\.|\+', date_iso_formated)
part2 = part2.ljust(6, '0')[:6]
# timezone fix: 0200 >>> 02:00
if len(part3) == 4:
part3 = part3[1:2].rjust(2, '0') + ':' + part3[3:4].rjust(2, '0')
date_iso_formated = part1 + "." + part2 + "+" + part3
dateObj = datetime.datetime.fromisoformat(date_iso_formated)
dateObj = parse(self._json['Updated'])
return_ts = int("%.0f" % dateObj.timestamp())
else:
# use file timestamp last modification of the json file
@@ -335,9 +344,9 @@ class PositionFile:
return_pass = password_file.read()
password_file.close()
except OSError as error:
logging.error(f"[webgpsmap] OS error: {format(error)}")
logging.error(f"[webgpsmap] OS error loading password: {password_file_path} - error: {format(error)}")
except:
logging.error(f"[webgpsmap] Unexpected error: {sys.exc_info()[0]}")
logging.error(f"[webgpsmap] Unexpected error loading password: {password_file_path} - error: {sys.exc_info()[0]}")
raise
return return_pass

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