Compare commits

...

16 Commits

Author SHA1 Message Date
Kevin F c880b6fa7a Removed webUI and unused god objects
Remove defunct requirements
Removed more defunct code
Prepare onionrvalues for new release
disable unixtransport by default
2022-08-06 12:01:32 -05:00
Kevin F 228d3cbe35 Fix tor announcing to itsself 2022-08-02 21:06:30 -05:00
Kevin F 9864fa5040 Tweaked some gossip timeout values and fixed tor not storing full address in config 2022-07-31 12:23:01 -05:00
Kevin F 3a7e378d8b Added block database cleaner 2022-07-31 00:32:43 -05:00
Kevin F f220e398f1 Substantial bug fixes and performance improvements 2022-07-30 15:41:11 -05:00
Kevin F 8bd4a4c524 Fixed stemout blocking and performance issues 2022-07-28 16:10:36 -05:00
Kevin F c663be30f1 bump flask 2022-07-28 16:09:31 -05:00
Kevin F 90176e43fb Implemented non-dandelion uploading to improve network performance 2022-07-26 12:45:48 -05:00
Kevin F 9b6d1f5dbd Fix diffuseblocks blocking the event loop 2022-07-25 14:37:39 -05:00
Kevin F e7daaf576f Added logic for less strict dandelion++ fanout in order to enable smaller networks to function more quickly 2022-07-24 00:37:10 -05:00
Kevin F 84c13ade51 gossip fixes 2022-07-19 00:32:54 -05:00
Kevin F d2b5298bc6 Fix conflicting module names in transports 2022-07-17 00:01:07 -05:00
Kevin F d11d12b67f async bug fixes 2022-07-11 10:27:13 -05:00
Kevin F 1eadb4bf6e bumped dependencies 2022-07-10 22:28:30 -05:00
Kevin F 64a88118bd added unix transport for testing 2022-06-26 14:43:16 -05:00
Kevin F b25e376349 gossip bug and performance fixes 2022-06-26 00:34:49 -05:00
224 changed files with 863 additions and 20763 deletions

View File

@ -1,18 +1,11 @@
urllib3==1.26.7
requests==2.26.0
PyNaCl==1.5.0
gevent==21.12.0
Flask==2.1.2
PySocks==1.7.1
stem==1.8.0
deadsimplekv==0.3.2
unpaddedbase32==0.2.0
toomanyobjs==1.1.0
niceware==0.2.1
psutil==5.9.1
filenuke==0.0.0
watchdog==2.1.6
ujson==5.3.0
cffi==1.14.4
ujson==5.4.0
cffi==1.15.1
onionrblocks==7.0.0
ordered_set==4.1.0
ordered-set==4.1.0

View File

@ -2,507 +2,39 @@
# This file is autogenerated by pip-compile with python 3.10
# To update, run:
#
# pip-compile --generate-hashes
# pip-compile
#
certifi==2018.11.29 \
--hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
--hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033
# via requests
cffi==1.14.4 \
--hash=sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e \
--hash=sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d \
--hash=sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a \
--hash=sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec \
--hash=sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362 \
--hash=sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668 \
--hash=sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c \
--hash=sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b \
--hash=sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06 \
--hash=sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698 \
--hash=sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2 \
--hash=sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c \
--hash=sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7 \
--hash=sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009 \
--hash=sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03 \
--hash=sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b \
--hash=sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e \
--hash=sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909 \
--hash=sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53 \
--hash=sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35 \
--hash=sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26 \
--hash=sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b \
--hash=sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01 \
--hash=sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb \
--hash=sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293 \
--hash=sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd \
--hash=sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d \
--hash=sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3 \
--hash=sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d \
--hash=sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e \
--hash=sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca \
--hash=sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d \
--hash=sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775 \
--hash=sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375 \
--hash=sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b \
--hash=sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b \
--hash=sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f
cffi==1.15.1
# via
# -r requirements.in
# pynacl
charset-normalizer==2.0.9 \
--hash=sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721 \
--hash=sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c
# via requests
click==8.0.3 \
--hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \
--hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b
# via flask
deadsimplekv==0.3.2 \
--hash=sha256:a725f4a9d1156ebb66b7535ac150006881e0365b715e34e3709214827b8b0c4c \
--hash=sha256:df00262d26c3dcfecb710425a7413059480d8cf026216042d7cbffb8514818b2
filenuke==0.0.0
# via -r requirements.in
filenuke==0.0.0 \
--hash=sha256:147011c0125121469cae0a8a7f4df399f470e54aa29a08f2d2c099bf0118dcee \
--hash=sha256:c55535dcecfdb27c5f4ce664d46e115950b5429763b5db75c198053646177f8f
# via -r requirements.in
flask==2.1.2 \
--hash=sha256:315ded2ddf8a6281567edb27393010fe3406188bafbfe65a3339d5787d89e477 \
--hash=sha256:fad5b446feb0d6db6aec0c3184d16a8c1f6c3e464b511649c8918a9be100b4fe
# via -r requirements.in
gevent==21.12.0 \
--hash=sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397 \
--hash=sha256:01928770972181ad8866ee37ea3504f1824587b188fcab782ef1619ce7538766 \
--hash=sha256:05c5e8a50cd6868dd36536c92fb4468d18090e801bd63611593c0717bab63692 \
--hash=sha256:08b4c17064e28f4eb85604486abc89f442c7407d2aed249cf54544ce5c9baee6 \
--hash=sha256:177f93a3a90f46a5009e0841fef561601e5c637ba4332ab8572edd96af650101 \
--hash=sha256:22ce1f38fdfe2149ffe8ec2131ca45281791c1e464db34b3b4321ae9d8d2efbb \
--hash=sha256:24d3550fbaeef5fddd794819c2853bca45a86c3d64a056a2c268d981518220d1 \
--hash=sha256:2afa3f3ad528155433f6ac8bd64fa5cc303855b97004416ec719a6b1ca179481 \
--hash=sha256:2bcec9f80196c751fdcf389ca9f7141e7b0db960d8465ed79be5e685bfcad682 \
--hash=sha256:2cfff82f05f14b7f5d9ed53ccb7a609ae8604df522bb05c971bca78ec9d8b2b9 \
--hash=sha256:3baeeccc4791ba3f8db27179dff11855a8f9210ddd754f6c9b48e0d2561c2aea \
--hash=sha256:3c012c73e6c61f13c75e3a4869dbe6a2ffa025f103421a6de9c85e627e7477b1 \
--hash=sha256:3dad62f55fad839d498c801e139481348991cee6e1c7706041b5fe096cb6a279 \
--hash=sha256:542ae891e2aa217d2cf6d8446538fcd2f3263a40eec123b970b899bac391c47a \
--hash=sha256:6a02a88723ed3f0fd92cbf1df3c4cd2fbd87d82b0a4bac3e36a8875923115214 \
--hash=sha256:74fc1ef16b86616cfddcc74f7292642b0f72dde4dd95aebf4c45bb236744be54 \
--hash=sha256:7909780f0cf18a1fc32aafd8c8e130cdd93c6e285b11263f7f2d1a0f3678bc50 \
--hash=sha256:7ccffcf708094564e442ac6fde46f0ae9e40015cb69d995f4b39cc29a7643881 \
--hash=sha256:8c21cb5c9f4e14d75b3fe0b143ec875d7dbd1495fad6d49704b00e57e781ee0f \
--hash=sha256:973749bacb7bc4f4181a8fb2a7e0e2ff44038de56d08e856dd54a5ac1d7331b4 \
--hash=sha256:9d86438ede1cbe0fde6ef4cc3f72bf2f1ecc9630d8b633ff344a3aeeca272cdd \
--hash=sha256:9f9652d1e4062d4b5b5a0a49ff679fa890430b5f76969d35dccb2df114c55e0f \
--hash=sha256:a5ad4ed8afa0a71e1927623589f06a9b5e8b5e77810be3125cb4d93050d3fd1f \
--hash=sha256:b7709c64afa8bb3000c28bb91ec42c79594a7cb0f322e20427d57f9762366a5b \
--hash=sha256:bb5cb8db753469c7a9a0b8a972d2660fe851aa06eee699a1ca42988afb0aaa02 \
--hash=sha256:c43f081cbca41d27fd8fef9c6a32cf83cb979345b20abc07bf68df165cdadb24 \
--hash=sha256:cc2fef0f98ee180704cf95ec84f2bc2d86c6c3711bb6b6740d74e0afe708b62c \
--hash=sha256:da8d2d51a49b2a5beb02ad619ca9ddbef806ef4870ba04e5ac7b8b41a5b61db3 \
--hash=sha256:e1899b921219fc8959ff9afb94dae36be82e0769ed13d330a393594d478a0b3a \
--hash=sha256:eae3c46f9484eaacd67ffcdf4eaf6ca830f587edd543613b0f5c4eb3c11d052d \
--hash=sha256:ec21f9eaaa6a7b1e62da786132d6788675b314f25f98d9541f1bf00584ed4749 \
--hash=sha256:f289fae643a3f1c3b909d6b033e6921b05234a4907e9c9c8c3f1fe403e6ac452 \
--hash=sha256:f48b64578c367b91fa793bf8eaaaf4995cb93c8bc45860e473bf868070ad094e
# via -r requirements.in
greenlet==1.1.2 \
--hash=sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3 \
--hash=sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711 \
--hash=sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd \
--hash=sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073 \
--hash=sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708 \
--hash=sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67 \
--hash=sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23 \
--hash=sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1 \
--hash=sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08 \
--hash=sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd \
--hash=sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2 \
--hash=sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa \
--hash=sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8 \
--hash=sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40 \
--hash=sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab \
--hash=sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6 \
--hash=sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc \
--hash=sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b \
--hash=sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e \
--hash=sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963 \
--hash=sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3 \
--hash=sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d \
--hash=sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d \
--hash=sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe \
--hash=sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28 \
--hash=sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3 \
--hash=sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e \
--hash=sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c \
--hash=sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d \
--hash=sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0 \
--hash=sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497 \
--hash=sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee \
--hash=sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713 \
--hash=sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58 \
--hash=sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a \
--hash=sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06 \
--hash=sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88 \
--hash=sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965 \
--hash=sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f \
--hash=sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4 \
--hash=sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5 \
--hash=sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c \
--hash=sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a \
--hash=sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1 \
--hash=sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43 \
--hash=sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627 \
--hash=sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b \
--hash=sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168 \
--hash=sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d \
--hash=sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5 \
--hash=sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478 \
--hash=sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf \
--hash=sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce \
--hash=sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c \
--hash=sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b
# via gevent
idna==2.7 \
--hash=sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e \
--hash=sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16
# via requests
itsdangerous==2.0.1 \
--hash=sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c \
--hash=sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0
# via flask
jinja2==3.0.3 \
--hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \
--hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7
# via flask
kasten==3.0.0 \
--hash=sha256:52894af46d6e1339f0d5fa8961892b292f99176848bce11877fe4a435b6782e5 \
--hash=sha256:b22ebdc5f475c2ef9ab74abc36552add0b37732a7ce2be6bd7977ee41b2163b4
kasten==3.0.0
# via onionrblocks
markupsafe==2.0.1 \
--hash=sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298 \
--hash=sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64 \
--hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \
--hash=sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194 \
--hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \
--hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \
--hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \
--hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \
--hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \
--hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \
--hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \
--hash=sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a \
--hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \
--hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \
--hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \
--hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \
--hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \
--hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \
--hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \
--hash=sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047 \
--hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \
--hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \
--hash=sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b \
--hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \
--hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \
--hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \
--hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \
--hash=sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1 \
--hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \
--hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \
--hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \
--hash=sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee \
--hash=sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f \
--hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \
--hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \
--hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \
--hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \
--hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \
--hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \
--hash=sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86 \
--hash=sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6 \
--hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \
--hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \
--hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \
--hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \
--hash=sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e \
--hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \
--hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \
--hash=sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f \
--hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \
--hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \
--hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \
--hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \
--hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \
--hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \
--hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \
--hash=sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a \
--hash=sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207 \
--hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \
--hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \
--hash=sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd \
--hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \
--hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \
--hash=sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9 \
--hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \
--hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \
--hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \
--hash=sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51 \
--hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872
# via jinja2
mimcvdf==1.2.1 \
--hash=sha256:7c837c46cfb9dce4ba895bc706a69646d4d5185c66aeaa333b5cfaa9a7d06dc4
mimcvdf==1.2.1
# via kasten
msgpack==1.0.3 \
--hash=sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc \
--hash=sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147 \
--hash=sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3 \
--hash=sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba \
--hash=sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39 \
--hash=sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85 \
--hash=sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9 \
--hash=sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a \
--hash=sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec \
--hash=sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88 \
--hash=sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e \
--hash=sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a \
--hash=sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b \
--hash=sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1 \
--hash=sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3 \
--hash=sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef \
--hash=sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079 \
--hash=sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52 \
--hash=sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a \
--hash=sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a \
--hash=sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4 \
--hash=sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996 \
--hash=sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73 \
--hash=sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a \
--hash=sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920 \
--hash=sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7 \
--hash=sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d \
--hash=sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770 \
--hash=sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50 \
--hash=sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2 \
--hash=sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2 \
--hash=sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d \
--hash=sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea \
--hash=sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611
msgpack==1.0.3
# via kasten
niceware==0.2.1 \
--hash=sha256:0f8b192f2a1e800e068474f6e208be9c7e2857664b33a96f4045340de4e5c69c \
--hash=sha256:cf2dc0e1567d36d067c61b32fed0f1b9c4534ed511f9eeead4ba548d03b5c9eb
niceware==0.2.1
# via -r requirements.in
onionrblocks==7.0.0 \
--hash=sha256:53e90964371076d9daf2ed0790b21f174ef3321f4f1808209cc6dd9b7ff6d8ff \
--hash=sha256:54af28d0be856209525646c4ef9f977f95f0ae1329b2cc023b351317c9d0eef7
onionrblocks==7.0.0
# via -r requirements.in
ordered-set==4.1.0 \
--hash=sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562 \
--hash=sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8
ordered-set==4.1.0
# via -r requirements.in
psutil==5.9.1 \
--hash=sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685 \
--hash=sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc \
--hash=sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36 \
--hash=sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1 \
--hash=sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329 \
--hash=sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81 \
--hash=sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de \
--hash=sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4 \
--hash=sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574 \
--hash=sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237 \
--hash=sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22 \
--hash=sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b \
--hash=sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0 \
--hash=sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954 \
--hash=sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021 \
--hash=sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537 \
--hash=sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87 \
--hash=sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0 \
--hash=sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc \
--hash=sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af \
--hash=sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4 \
--hash=sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453 \
--hash=sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689 \
--hash=sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8 \
--hash=sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680 \
--hash=sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e \
--hash=sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9 \
--hash=sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b \
--hash=sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d \
--hash=sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2 \
--hash=sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5 \
--hash=sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676
psutil==5.9.1
# via -r requirements.in
pycparser==2.19 \
--hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3
pycparser==2.19
# via cffi
pynacl==1.5.0 \
--hash=sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858 \
--hash=sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d \
--hash=sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93 \
--hash=sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1 \
--hash=sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92 \
--hash=sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff \
--hash=sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba \
--hash=sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394 \
--hash=sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b \
--hash=sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543
pynacl==1.5.0
# via
# -r requirements.in
# onionrblocks
pysocks==1.7.1 \
--hash=sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
pysocks==1.7.1
# via -r requirements.in
requests==2.26.0 \
--hash=sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24 \
--hash=sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7
stem==1.8.0
# via -r requirements.in
stem==1.8.0 \
--hash=sha256:a0b48ea6224e95f22aa34c0bc3415f0eb4667ddeae3dfb5e32a6920c185568c2
ujson==5.4.0
# via -r requirements.in
toomanyobjs==1.1.0 \
--hash=sha256:99e27468f9dad19127be9e2fb086b42acd69aed9ad7e63cef74d6e4389be0534
unpaddedbase32==0.2.0
# via -r requirements.in
ujson==5.3.0 \
--hash=sha256:034c07399dff35385ecc53caf9b1f12b3e203834de27b723daeb2cbb3e02ee7f \
--hash=sha256:089965f964d17905c48cdca88b982d525165e549b438ac86f194c6a9d852fd69 \
--hash=sha256:1358621686ddfda55171fc98c171bf5b1a80ce4d444134b70e1e449925fa014f \
--hash=sha256:151faa9085c10351a04aea959a2bc25dfa2e21af26d9b614a221d045b7923ea4 \
--hash=sha256:285082924747958aa69e1dc2146c01db6b0921a0bb04b595beefe7fcffaffaf9 \
--hash=sha256:287dea79473ce4941598c45dc34f9f692d48d7863b451541c5ce960ab54465fb \
--hash=sha256:2db7cbe415d7329b9bff029a83851d1077836ec728fe1c32be34c9c3a5017ab2 \
--hash=sha256:34592a3c9370745b093ebca60aee6d32f8e7abe3d5c12d54c7dba0b2f81cd863 \
--hash=sha256:47bf966e1041ae8e568d7e8eb421d72d0521c30c28306b76c256832553e316c6 \
--hash=sha256:48bed7c1f95484644a2cc658efff4d1e75b8c806f6ef2b5c815f59e1cbe0d039 \
--hash=sha256:4dc79db757b0dfa23a111a4573827a6ef57de65dbe8cdb202e45cf9ddf06aad5 \
--hash=sha256:510c3705b29bc3753ec9e6073b99000160320c1cf6e035884295401acb474dfa \
--hash=sha256:5192505798a5734a85c763eff11e6f6072d3595c337b52f72922b4e22fe66e2e \
--hash=sha256:522b1d60872bb6368c14ac538adb55ca9d6c39a7a962832819ef1aafb3446ff5 \
--hash=sha256:563b7ed1e789f763410c49e6fab51d61982eb94088b25338e65b89ad20b6b107 \
--hash=sha256:5700a179abacbdc8609737e595a598b7f107cd68615ded3f922f4c0d4b6009d6 \
--hash=sha256:5a87e1c05f1efc23c67bfa26be79f12c1f59f71a586b396068d5cf7eb78a2635 \
--hash=sha256:612015c6e5a9bf041b89f1eaa8ab8682469b3a745a00c7c95bbbee8080f6b346 \
--hash=sha256:66f857d8b8d7ea44e3fd5f2b7e471334f24b735423729771f5a7a7f69ab645ed \
--hash=sha256:6aba1e39ffdd83ec14832ea25bbb18266fea46bc69b8c0acbd996495826c0e6f \
--hash=sha256:6c5d19fbdd29d5080926c863ba89591a2d3dbf592ea35b456cb2996004433d11 \
--hash=sha256:73636001055667bbcc6a73b232da1d272f68a49a1f192efbe99e99ddf8ef1d21 \
--hash=sha256:7455fc3d69315149b95fd011c01496a5e9442c9e7c4d202bed87c5c2e449ed05 \
--hash=sha256:7d2cb50aa526032b8812975c3832058763ee50e1dc3a1302431ed9d0922c3a1b \
--hash=sha256:865225a85e4ce48754d0036fdc0eb796b4aaf4f1e928f0efb9b4e1c081647a4c \
--hash=sha256:8a2cbb044bc6e6764b9a089a2079432b8bd576dbff5faa808b562a8f3c97452b \
--hash=sha256:8c734982d6560356c173817576a1f3fa074a2d2b993e63bffa69105ae9ec144b \
--hash=sha256:8dd74570fe59c738d4dc12d44eb89538b0b01fae9dda6cfe3ff3f6934877cf35 \
--hash=sha256:972c1850cc52e57ccdea70e3c069e2da5c6090e3ee18d167dff2618a8d7dd127 \
--hash=sha256:a014531468b78c031aa04e5ca8b64385a6edb48a2e66ebf11093213c678fc383 \
--hash=sha256:a4fe193050b519ace09f7d053def30b99deadf650c18a8a874ea0f6c9a2992bc \
--hash=sha256:a609bb1cdda9748e6a8363039926dee5ea2bcc073412279615560b967f92a524 \
--hash=sha256:a68d5a8a46712ffe86db8ae1b4311714db534725521c71fd4c9e1cd062dae9a4 \
--hash=sha256:a720b6eff73415249a3dd02e2b1b337de31bb9fa8220bd572dffba23066e538c \
--hash=sha256:a933b3a238a48162c382e0ac338b97663d044b0485021b6670565a81e7b7ec98 \
--hash=sha256:ab938777b3ac0372231ee654a7f6a13787e587b1ca268d8aa7e6fb6846e477d0 \
--hash=sha256:b3e6431812d8008dce7b2546b1276f649f6c9aa44617762ebd3529a25092816c \
--hash=sha256:b926f2f7a266db8f2c46498f0c2c9fcc7e53c8e0fa8bff7f08ad9c044723a2ec \
--hash=sha256:bad1471ccfa8d100a0bc513c6db587c38de99384f2aa54eec1016a131d63d3d9 \
--hash=sha256:c1408ea1704017289c3023928065233b90953aae3e1d7d06d6d6db667e9fe159 \
--hash=sha256:c5696c99a7dd567566c18490e8e346b2657967feb1e3c2004e91dbb253db0894 \
--hash=sha256:ca5eced4ae4ba1e2c9539fca6451694d31e0243de2acfcd6965e2b6e159ba29b \
--hash=sha256:d1fab398734634f4b412512ed230d45522fc9f3dd9ca169f579474a491f662aa \
--hash=sha256:d45e86101a5cddd295d5870b02244fc87ecd9b8936f440acbd2bb30b4c1fe23c \
--hash=sha256:d4830c8df958c45c16dfc43c8353403efd7f1a8e39b91a7e0e848d55b7fa8b48 \
--hash=sha256:d553f31bceda492c2bda37f48873820d28f07608ae14409c5e9d6c3aa6694840 \
--hash=sha256:decd32e8d7f934dde484e43431f60b069e87bb30a3a7e186cb6bd69caa0418f3 \
--hash=sha256:e7961c493a982c03cffc9ce4dc2b23bed1375352296f946cc36ddeb5145fa62c \
--hash=sha256:ed9809bc36292e0d3632d50aae497b5827c1a2e07158f7d4d5c53e8e8662bf66 \
--hash=sha256:f615ee181b813c8f50a57d55354d0c0304a0be066962efdbef6f44517b26e3b2
# via -r requirements.in
unpaddedbase32==0.2.0 \
--hash=sha256:4aacee75f8fd6c8cf129842ecba45ca59c11bfb13dae19d86f32b48fa3715403 \
--hash=sha256:b7b780c31d27d55e66abf6c221216a35690ee8892c2daacff7f2528e229bd9c3
# via -r requirements.in
urllib3==1.26.7 \
--hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \
--hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844
# via
# -r requirements.in
# requests
watchdog==2.1.6 \
--hash=sha256:25fb5240b195d17de949588628fdf93032ebf163524ef08933db0ea1f99bd685 \
--hash=sha256:3386b367e950a11b0568062b70cc026c6f645428a698d33d39e013aaeda4cc04 \
--hash=sha256:3becdb380d8916c873ad512f1701f8a92ce79ec6978ffde92919fd18d41da7fb \
--hash=sha256:4ae38bf8ba6f39d5b83f78661273216e7db5b00f08be7592062cb1fc8b8ba542 \
--hash=sha256:8047da932432aa32c515ec1447ea79ce578d0559362ca3605f8e9568f844e3c6 \
--hash=sha256:8f1c00aa35f504197561060ca4c21d3cc079ba29cf6dd2fe61024c70160c990b \
--hash=sha256:922a69fa533cb0c793b483becaaa0845f655151e7256ec73630a1b2e9ebcb660 \
--hash=sha256:9693f35162dc6208d10b10ddf0458cc09ad70c30ba689d9206e02cd836ce28a3 \
--hash=sha256:a0f1c7edf116a12f7245be06120b1852275f9506a7d90227648b250755a03923 \
--hash=sha256:a36e75df6c767cbf46f61a91c70b3ba71811dfa0aca4a324d9407a06a8b7a2e7 \
--hash=sha256:aba5c812f8ee8a3ff3be51887ca2d55fb8e268439ed44110d3846e4229eb0e8b \
--hash=sha256:ad6f1796e37db2223d2a3f302f586f74c72c630b48a9872c1e7ae8e92e0ab669 \
--hash=sha256:ae67501c95606072aafa865b6ed47343ac6484472a2f95490ba151f6347acfc2 \
--hash=sha256:b2fcf9402fde2672545b139694284dc3b665fd1be660d73eca6805197ef776a3 \
--hash=sha256:b52b88021b9541a60531142b0a451baca08d28b74a723d0c99b13c8c8d48d604 \
--hash=sha256:b7d336912853d7b77f9b2c24eeed6a5065d0a0cc0d3b6a5a45ad6d1d05fb8cd8 \
--hash=sha256:bd9ba4f332cf57b2c1f698be0728c020399ef3040577cde2939f2e045b39c1e5 \
--hash=sha256:be9be735f827820a06340dff2ddea1fb7234561fa5e6300a62fe7f54d40546a0 \
--hash=sha256:cca7741c0fcc765568350cb139e92b7f9f3c9a08c4f32591d18ab0a6ac9e71b6 \
--hash=sha256:d0d19fb2441947b58fbf91336638c2b9f4cc98e05e1045404d7a4cb7cddc7a65 \
--hash=sha256:e02794ac791662a5eafc6ffeaf9bcc149035a0e48eb0a9d40a8feb4622605a3d \
--hash=sha256:e0f30db709c939cabf64a6dc5babb276e6d823fd84464ab916f9b9ba5623ca15 \
--hash=sha256:e92c2d33858c8f560671b448205a268096e17870dcf60a9bb3ac7bfbafb7f5f9
# via -r requirements.in
werkzeug==2.0.2 \
--hash=sha256:63d3dc1cf60e7b7e35e97fa9861f7397283b75d765afcaefd993d6046899de8f \
--hash=sha256:aa2bb6fc8dee8d6c504c0ac1e7f5f7dc5810a9903e793b6f715a9f015bdadb9a
# via flask
zope-event==4.4 \
--hash=sha256:69c27debad9bdacd9ce9b735dad382142281ac770c4a432b533d6d65c4614bcf \
--hash=sha256:d8e97d165fd5a0997b45f5303ae11ea3338becfe68c401dd88ffd2113fe5cae7
# via gevent
zope-interface==5.1.0 \
--hash=sha256:0103cba5ed09f27d2e3de7e48bb320338592e2fabc5ce1432cf33808eb2dfd8b \
--hash=sha256:14415d6979356629f1c386c8c4249b4d0082f2ea7f75871ebad2e29584bd16c5 \
--hash=sha256:1ae4693ccee94c6e0c88a4568fb3b34af8871c60f5ba30cf9f94977ed0e53ddd \
--hash=sha256:1b87ed2dc05cb835138f6a6e3595593fea3564d712cb2eb2de963a41fd35758c \
--hash=sha256:269b27f60bcf45438e8683269f8ecd1235fa13e5411de93dae3b9ee4fe7f7bc7 \
--hash=sha256:27d287e61639d692563d9dab76bafe071fbeb26818dd6a32a0022f3f7ca884b5 \
--hash=sha256:39106649c3082972106f930766ae23d1464a73b7d30b3698c986f74bf1256a34 \
--hash=sha256:40e4c42bd27ed3c11b2c983fecfb03356fae1209de10686d03c02c8696a1d90e \
--hash=sha256:461d4339b3b8f3335d7e2c90ce335eb275488c587b61aca4b305196dde2ff086 \
--hash=sha256:4f98f70328bc788c86a6a1a8a14b0ea979f81ae6015dd6c72978f1feff70ecda \
--hash=sha256:558a20a0845d1a5dc6ff87cd0f63d7dac982d7c3be05d2ffb6322a87c17fa286 \
--hash=sha256:562dccd37acec149458c1791da459f130c6cf8902c94c93b8d47c6337b9fb826 \
--hash=sha256:5e86c66a6dea8ab6152e83b0facc856dc4d435fe0f872f01d66ce0a2131b7f1d \
--hash=sha256:60a207efcd8c11d6bbeb7862e33418fba4e4ad79846d88d160d7231fcb42a5ee \
--hash=sha256:645a7092b77fdbc3f68d3cc98f9d3e71510e419f54019d6e282328c0dd140dcd \
--hash=sha256:6874367586c020705a44eecdad5d6b587c64b892e34305bb6ed87c9bbe22a5e9 \
--hash=sha256:74bf0a4f9091131de09286f9a605db449840e313753949fe07c8d0fe7659ad1e \
--hash=sha256:7b726194f938791a6691c7592c8b9e805fc6d1b9632a833b9c0640828cd49cbc \
--hash=sha256:8149ded7f90154fdc1a40e0c8975df58041a6f693b8f7edcd9348484e9dc17fe \
--hash=sha256:8cccf7057c7d19064a9e27660f5aec4e5c4001ffcf653a47531bde19b5aa2a8a \
--hash=sha256:911714b08b63d155f9c948da2b5534b223a1a4fc50bb67139ab68b277c938578 \
--hash=sha256:a5f8f85986197d1dd6444763c4a15c991bfed86d835a1f6f7d476f7198d5f56a \
--hash=sha256:a744132d0abaa854d1aad50ba9bc64e79c6f835b3e92521db4235a1991176813 \
--hash=sha256:af2c14efc0bb0e91af63d00080ccc067866fb8cbbaca2b0438ab4105f5e0f08d \
--hash=sha256:b054eb0a8aa712c8e9030065a59b5e6a5cf0746ecdb5f087cca5ec7685690c19 \
--hash=sha256:b0becb75418f8a130e9d465e718316cd17c7a8acce6fe8fe07adc72762bee425 \
--hash=sha256:b1d2ed1cbda2ae107283befd9284e650d840f8f7568cb9060b5466d25dc48975 \
--hash=sha256:ba4261c8ad00b49d48bbb3b5af388bb7576edfc0ca50a49c11dcb77caa1d897e \
--hash=sha256:d1fe9d7d09bb07228650903d6a9dc48ea649e3b8c69b1d263419cc722b3938e8 \
--hash=sha256:d7804f6a71fc2dda888ef2de266727ec2f3915373d5a785ed4ddc603bbc91e08 \
--hash=sha256:da2844fba024dd58eaa712561da47dcd1e7ad544a257482392472eae1c86d5e5 \
--hash=sha256:dcefc97d1daf8d55199420e9162ab584ed0893a109f45e438b9794ced44c9fd0 \
--hash=sha256:dd98c436a1fc56f48c70882cc243df89ad036210d871c7427dc164b31500dc11 \
--hash=sha256:e74671e43ed4569fbd7989e5eecc7d06dc134b571872ab1d5a88f4a123814e9f \
--hash=sha256:eb9b92f456ff3ec746cd4935b73c1117538d6124b8617bc0fe6fda0b3816e345 \
--hash=sha256:ebb4e637a1fb861c34e48a00d03cffa9234f42bef923aec44e5625ffb9a8e8f9 \
--hash=sha256:ef739fe89e7f43fb6494a43b1878a36273e5924869ba1d866f752c5812ae8d58 \
--hash=sha256:f40db0e02a8157d2b90857c24d89b6310f9b6c3642369852cdc3b5ac49b92afc \
--hash=sha256:f68bf937f113b88c866d090fea0bc52a098695173fc613b055a17ff0cf9683b6 \
--hash=sha256:fb55c182a3f7b84c1a2d6de5fa7b1a05d4660d866b91dbf8d74549c57a1499e8
# via gevent
# WARNING: The following packages were not pinned, but pip requires them to be
# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
# setuptools

View File

@ -46,14 +46,6 @@ locale.setlocale(locale.LC_ALL, '') # noqa
ran_as_script = False
if __name__ == "__main__": ran_as_script = True
# Import standard libraries
try:
from onionrutils import dependencycheck # noqa
except ModuleNotFoundError as e:
print('Missing requirement: ' + str(e) + ' installed')
sys.exit(1)
# Import 3rd party libraries
from filenuke import nuke # noqa

View File

@ -1,9 +0,0 @@
# API Servers
Contains the WSGI servers Onionr uses for remote peer communication and local daemon control
## Files
* \_\_init\_\_.py: Exposes the server classes
* private: Contains the client API (the server used to interact with the local Onionr daemon, and view the web UI)
* public: Contains the public API (the server used by remote peers to talk to our daemon)

View File

@ -1,9 +0,0 @@
"""Flask WSGI apps for the public and private API servers.
Public is net-facing server meant for other nodes
Private is meant for controlling and accessing this node
"""
from . import private
private_api = private.private_api

View File

@ -1,8 +0,0 @@
# Private API Server
Private API server, used to access the web interface locally and control Onionr
## Files
* \_\_init\_\_.py: Sets up the server and a few misc functions
* register_private_blueprints.py: Adds in flask blueprints for various sub-APIs

View File

@ -1,121 +0,0 @@
"""Onionr - Private P2P Communication.
This file handles all incoming http requests to the client, using Flask
"""
from typing import Dict
from typing import Set
from typing import TYPE_CHECKING
import hmac
import flask
from gevent.pywsgi import WSGIServer
from onionrutils import epoch
import httpapi
from filepaths import private_API_host_file
import logger
from onionrutils import waitforsetvar
from . import register_private_blueprints
import config
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
class PrivateAPI:
"""Client HTTP api for controlling onionr and using UI."""
callbacks: Dict[str, Dict] = {'public': {}, 'private': {}}
def __init__(self):
"""Initialize the api server, preping variables for later use.
This initialization defines all of the API entry points
and handlers for the endpoints and errors
This also saves the used host (random localhost IP address)
to the data folder in host.txt
"""
self.config = config
self.startTime = epoch.get_epoch()
app = flask.Flask(__name__)
self.httpServer = ''
self.queueResponse = {}
register_private_blueprints.register_private_blueprints(self, app)
httpapi.load_plugin_blueprints(app)
self.app = app
def start(self):
"""Start client gevent API web server with flask client app."""
fd_handler = httpapi.fdsafehandler.FDSafeHandler
self.clientToken = config.get('client.webpassword')
if config.get('general.bind_address'):
with open(private_API_host_file, 'w') as bindFile:
bindFile.write(config.get('general.bind_address'))
self.host = config.get('general.bind_address')
else:
self.host = httpapi.apiutils.setbindip.set_bind_IP(
private_API_host_file)
bind_port = int(config.get('client.client.port', 59496))
self.bindPort = bind_port
self.httpServer = WSGIServer((self.host, self.bindPort),
self.app, log=None,
handler_class=fd_handler)
logger.info(f'Running API on {self.host}:{self.bindPort}', terminal=True)
self.httpServer.serve_forever()
def setPublicAPIInstance(self, inst):
"""Dynamically set public API instance."""
self.publicAPI = inst
def validateToken(self, token):
"""Validate that the client token matches the given token.
Used to prevent CSRF and other attacks.
"""
if not self.clientToken:
logger.error("client password needs to be set")
return False
try:
return hmac.compare_digest(self.clientToken, token)
except TypeError:
return False
def getUptime(self) -> int:
"""Safely wait for uptime to be set and return it."""
while True:
try:
return epoch.get_epoch() - self.startTime
except (AttributeError, NameError):
# Don't error on race condition with startup
pass
def getBlockData(self, bHash, decrypt=False, raw=False,
headerOnly=False) -> bytes:
"""Returns block data bytes."""
return self.get_block_data.get_block_data(bHash,
decrypt=decrypt,
raw=raw,
headerOnly=headerOnly)
private_api = PrivateAPI()

View File

@ -1,46 +0,0 @@
"""Onionr - Private P2P Communication.
This file registers blueprints for the private api server
"""
from threading import Thread
from gevent import sleep
from httpapi import security, friendsapi, configapi
from httpapi import miscclientapi, apiutils
from httpapi import themeapi
from httpapi import fileoffsetreader
from httpapi.sse.private import private_sse_blueprint
from httpapi import addblock
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def register_private_blueprints(private_api, app):
"""Register private API plask blueprints."""
app.register_blueprint(security.client.ClientAPISecurity(
private_api).client_api_security_bp)
app.register_blueprint(friendsapi.friends)
app.register_blueprint(configapi.config_BP)
app.register_blueprint(miscclientapi.endpoints.PrivateEndpoints(
private_api).private_endpoints_bp)
app.register_blueprint(apiutils.shutdown.shutdown_bp)
app.register_blueprint(miscclientapi.staticfiles.static_files_bp)
app.register_blueprint(themeapi.theme_blueprint)
app.register_blueprint(private_sse_blueprint)
app.register_blueprint(fileoffsetreader.offset_reader_api)
app.register_blueprint(addblock.blockapi_blueprint)
return app

View File

@ -5,40 +5,19 @@ from onionrblocks import Block
import db
from .dbpath import block_db_path
from .blockcleaner import clean_block_database
from .getblocks import get_blocks_after_timestamp, get_blocks_by_type
from .deleteblock import delete_block
block_storage_observers: List[Callable] = []
def add_block_to_db(block: Block):
# Raises db.DuplicateKey if dupe
db.set_if_new(block_db_path, block.id, block.raw)
for func in block_storage_observers:
func(block)
def get_blocks_by_type(block_type: str) -> "Generator[Block]":
block_db = db.get_db_obj(block_db_path, 'u')
for block_hash in db.list_keys(block_db_path):
block = Block(block_hash, block_db[block_hash], auto_verify=False)
if block.type == block_type:
yield block
def get_blocks_after_timestamp(
timestamp: int, block_type: str = '') -> "Generator[Block]":
block_db = db.get_db_obj(block_db_path, 'u')
for block_hash in db.list_keys(block_db_path):
block = Block(block_hash, block_db[block_hash], auto_verify=False)
if block.timestamp > timestamp:
if block_type:
if block_type == block.type:
yield block
else:
yield block
def has_block(block_hash):
return block_hash in db.list_keys(block_db_path)

View File

@ -0,0 +1,24 @@
from typing import Set
from onionrblocks import Block
import logger
from .deleteblock import delete_block
from .getblocks import get_blocks_after_timestamp
def clean_block_database():
"""Delete expired blocks from block db"""
remove_set: Set[bytes] = set()
block: Block
for block in get_blocks_after_timestamp(0):
try:
Block(block.id, block.raw, auto_verify=True)
except ValueError: # block expired
remove_set.add(block)
if len(remove_set):
logger.info(f"Cleaning {len(remove_set)} blocks", terminal=True)
[i for i in map(delete_block, remove_set)]

View File

@ -0,0 +1,10 @@
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from onionrblocks import Block
import db
from .dbpath import block_db_path
def delete_block(block: 'Block'): db.delete(block_db_path, block.id)

28
src/blockdb/getblocks.py Normal file
View File

@ -0,0 +1,28 @@
from typing import Generator
import db
from onionrblocks import Block
from .dbpath import block_db_path
def get_blocks_by_type(block_type: str) -> "Generator[Block]":
block_db = db.get_db_obj(block_db_path, 'u')
for block_hash in db.list_keys(block_db_path):
block = Block(block_hash, block_db[block_hash], auto_verify=False)
if block.type == block_type:
yield block
def get_blocks_after_timestamp(
timestamp: int, block_type: str = '') -> "Generator[Block]":
block_db = db.get_db_obj(block_db_path, 'u')
for block_hash in db.list_keys(block_db_path):
block = Block(block_hash, block_db[block_hash], auto_verify=False)
if block.timestamp > timestamp:
if block_type:
if block_type == block.type:
yield block
else:
yield block

View File

@ -24,6 +24,16 @@ def _do_timeout(func, *args):
return res
def delete(db_path, key):
def _delete(key):
with dbm.open(db_path, "c") as my_db:
del my_db[key]
try:
_do_timeout(_delete, key)
except KeyError:
pass
def set_if_new(db_path, key, value) -> bool:
def _set(key, value):
with dbm.open(db_path, "c") as my_db:

View File

@ -20,6 +20,7 @@ upload_list = home + 'upload-list.json'
config_file = home + 'config.json'
daemon_mark_file = home + '/daemon-true.txt'
lock_file = home + 'onionr.lock'
pid_file = home + 'onionr.pid'
site_cache = home + 'onionr-sites.txt'

View File

@ -44,10 +44,10 @@ def start_gossip_threads():
# There is a unified set so gossip logic is not repeated
add_onionr_thread(
gossip_server, 1, initial_sleep=0.2)
gossip_server, 1, 'gossip_server', initial_sleep=0.2)
threading.Thread(
target=start_gossip_client, daemon=True).start()
target=start_gossip_client, daemon=True, name="start_gossip_client").start()
onionrplugins.events.event('gossip_start', data=None, threaded=True)
for _ in range(BOOTSTRAP_ATTEMPTS):
onionrplugins.events.event(

View File

@ -22,6 +22,7 @@ if TYPE_CHECKING:
from ordered_set import OrderedSet
import logger
import config
import onionrplugins
from ..commands import GossipCommands
from gossip.dandelion.phase import DandelionPhase
@ -34,7 +35,7 @@ from .announce import do_announce
from .dandelionstem import stem_out
from .peerexchange import get_new_peers
from ..peerset import gossip_peer_set
from .streamblocks import stream_from_peers
from .streamblocks import stream_from_peers, stream_to_peer
"""
This program is free software: you can redistribute it and/or modify
@ -60,9 +61,9 @@ def block_queue_processing():
while not len(gossip_peer_set):
sleep(0.2)
if dandelion_phase.remaining_time() <= 15:
logger.debug("Sleeping", terminal=True)
#logger.debug("Sleeping", terminal=True)
sleep(dandelion_phase.remaining_time())
if dandelion_phase.is_stem_phase():
if dandelion_phase.is_stem_phase() and config.get('security.dandelion.enabled', True):
logger.debug("Entering stem phase", terminal=True)
try:
# Stem out blocks for (roughly) remaining epoch time
@ -73,8 +74,9 @@ def block_queue_processing():
logger.error(traceback.format_exc(), terminal=True)
pass
else:
logger.debug("Entering fluff phase", terminal=True)
#logger.debug("Entering fluff phase", terminal=True)
# Add block to primary block db, where the diffuser can read it
sleep(0.1)
store_blocks(dandelion_phase)
@ -87,10 +89,10 @@ def start_gossip_client():
"""
bl: Block
def _start_announce():
sleep(60)
do_announce()
Thread(target=_start_announce, daemon=True).start()
# Start a thread to announce our transport addresses to peers
add_onionr_thread(
do_announce,
300, 'do_announce', initial_sleep=5)
# Start a thread that runs every 1200 secs to
# Ask peers for a subset for their peer set
@ -99,16 +101,24 @@ def start_gossip_client():
# transport plugin handles the new peer
add_onionr_thread(
get_new_peers,
60, initial_sleep=120)
60, 'get_new_peers', initial_sleep=120)
# Start a new thread to stream blocks from peers
# These blocks are being diffused and are stored in
# the peer's block database
add_onionr_thread(
stream_from_peers,
3, initial_sleep=10
3, 'stream_from_peers', initial_sleep=10
)
# Start a thread to upload blocks, useful for when
# connectivity is poor or we are not allowing incoming
# connections on any transports
add_onionr_thread(
stream_to_peer,
10, 'stream_to_peer', initial_sleep=1)
# Blocks we receive or create through all means except
# Diffusal are put into block queues, we decide to either
# stem or diffuse a block from the queue based on the current

View File

@ -14,14 +14,19 @@ from ..peerset import gossip_peer_set
def do_announce():
"Announce with N peers of each identified transport"
per_transport = 4
peer_types = {}
count_for_peer = 0
def _announce(announce_peer: 'Peer', our_transport_address: str):
assert our_transport_address
try:
our_transport_address = our_transport_address.encode('utf-8') + b"\n"
except AttributeError:
pass
sock = announce_peer.get_socket(12)
sock.sendall(
command_to_byte(GossipCommands.ANNOUNCE) + our_transport_address)
our_transport_address = our_transport_address + b'\n'
sock = announce_peer.get_socket(40)
sock.sendall(command_to_byte(GossipCommands.ANNOUNCE))
sock.sendall(our_transport_address)
if int.from_bytes(sock.recv(1), 'big') != 1:
logger.warn(
f"Could not announce with {announce_peer.transport_address}")
@ -30,9 +35,6 @@ def do_announce():
while not len(gossip_peer_set):
sleep(1)
per_transport = 3
peer_types = {}
count_for_peer = 0
for peer in gossip_peer_set:
try:
count_for_peer = peer_types[peer.__class__]

View File

@ -1,12 +1,14 @@
from collections import deque
from queue import Empty, Queue
from time import sleep
from secrets import choice
import traceback
from typing import TYPE_CHECKING, Coroutine, Tuple, List
from typing import TYPE_CHECKING, Coroutine, List
from ordered_set import OrderedSet
import config
from onionrthreads import add_delayed_thread
from blockdb import add_block_to_db
import logger
@ -20,8 +22,6 @@ from ...peerset import gossip_peer_set
from .stemstream import do_stem_stream
if TYPE_CHECKING:
from onionrblocks import Block
from ...peer import Peer
from ...dandelion.phase import DandelionPhase
import socket
@ -56,7 +56,8 @@ async def _setup_edge(
if s.recv(1) == dandelion.StemAcceptResult.DENY:
raise StemConnectionDenied
except TimeoutError:
logger.debug("Peer timed out when establishing stem connection", terminal=True)
logger.debug(
"Peer timed out when establishing stem connection", terminal=True)
logger.debug(traceback.format_exc())
except StemConnectionDenied:
logger.debug(
@ -82,13 +83,17 @@ async def stem_out(d_phase: 'DandelionPhase'):
sleep(1)
return
not_enough_edges = False
strict_dandelion = config.get('security.dandelion.strict', True)
def blackhole_protection(q):
for bl in q:
add_block_to_db(bl)
# Spawn threads with deep copied block queue to add to db after time
# for black hole attack
for block_q in gossip_block_queues:
add_delayed_thread(
lambda q: set(map(add_block_to_db, q)),
BLACKHOLE_EVADE_TIMER_SECS, list(block_q.queue))
add_delayed_thread(blackhole_protection, BLACKHOLE_EVADE_TIMER_SECS, block_q.queue)
peer_sockets: List['socket.socket'] = []
stream_routines: List[Coroutine] = []
@ -98,28 +103,47 @@ async def stem_out(d_phase: 'DandelionPhase'):
tried_edges: "OrderedSet[Peer]" = OrderedSet()
while len(peer_sockets) < OUTBOUND_DANDELION_EDGES or not_enough_edges:
if gossip_block_queues[0].qsize() == 0 and \
gossip_block_queues[1].qsize() == 0:
sleep(1)
continue
try:
# Get a socket for stem out (makes sure they accept)
peer_sockets.append(await _setup_edge(gossip_peer_set, tried_edges))
peer_sockets.append(
await _setup_edge(gossip_peer_set, tried_edges))
except NotEnoughEdges:
# No possible edges at this point (edges < OUTBOUND_DANDELION_EDGE)
logger.warn("Making too few edges for stemout " +
"this is bad for anonymity if frequent.",
terminal=True)
not_enough_edges = True
#logger.debug(
# "Making too few edges for stemout " +
# "this is bad for anonymity if frequent.",
# terminal=True)
if strict_dandelion:
not_enough_edges = True
else:
if peer_sockets:
# if we have at least 1 peer,
# do dandelion anyway in non strict mode
# Allow poorly connected networks to communicate faster
for block in gossip_block_queues[1].queue:
gossip_block_queues[0].put_nowait(block)
else:
gossip_block_queues[1].queue = deque()
break
sleep(1)
else:
# Ran out of time for stem phase
if not d_phase.is_stem_phase() or d_phase.remaining_time() < 5:
logger.error(
"Did not stem out any blocks in time, " +
"if this happens regularly you may be under attack",
terminal=True)
terminal=False)
for s in peer_sockets:
if s:
s.close()
peer_sockets.clear()
break
# If above loop ran out of time or NotEnoughEdges, loops below will not execute
# If above loop ran out of time or NotEnoughEdges,
# loops below will not execute
for count, peer_socket in enumerate(peer_sockets):
stream_routines.append(

View File

@ -1,6 +1,11 @@
from asyncio import sleep
from threading import Thread
from queue import Empty
from typing import TYPE_CHECKING
from ...constants import BLOCK_MAX_SIZE, BLOCK_MAX_SIZE_LEN
import logger
from ...constants import BLOCK_ID_SIZE, BLOCK_MAX_SIZE, BLOCK_SIZE_LEN
if TYPE_CHECKING:
from queue import Queue
@ -17,14 +22,31 @@ async def do_stem_stream(
remaining_time = d_phase.remaining_time()
my_phase_id = d_phase.phase_id
with peer_socket:
while remaining_time > 5 and my_phase_id == d_phase.phase_id:
# Primary client component that communicate's with gossip.server.acceptstem
remaining_time = d_phase.remaining_time()
bl: 'Block' = block_queue.get(block=True, timeout=remaining_time)
while remaining_time > 1 and my_phase_id == d_phase.phase_id:
# Primary client component that communicate's with gossip.server.acceptstem
remaining_time = d_phase.remaining_time()
while remaining_time:
try:
# queues can't block because we're in async
bl = block_queue.get(block=False)
except Empty:
remaining_time = d_phase.remaining_time()
await sleep(1)
else:
break
logger.info("Sending block over dandelion++", terminal=True)
block_size = str(len(bl.raw)).zfill(BLOCK_MAX_SIZE_LEN)
peer_socket.sendall(bl.id)
peer_socket.sendall(block_size.encode('utf-8'))
peer_socket.sendall(bl.raw)
block_size = str(len(bl.raw)).zfill(BLOCK_SIZE_LEN)
def _send_it():
try:
with peer_socket:
try:
peer_socket.sendall(bl.id.zfill(BLOCK_ID_SIZE).encode('utf-8'))
except AttributeError:
peer_socket.sendall(bl.id.zfill(BLOCK_ID_SIZE))
peer_socket.sendall(block_size.encode('utf-8'))
peer_socket.sendall(bl.raw)
except OSError:
pass
Thread(target=_send_it, daemon=True, name="stemout block").start()

View File

@ -49,6 +49,7 @@ def _ask_peer(peer):
'address': peer,
'callback': connectpeer.connect_peer
}
logger.info("Got new peer from exchange " + peer.decode('utf-8'), terminal=True)
onionrevents.event('announce_rec', data=connect_data, threaded=True)
s.close()
@ -77,7 +78,9 @@ def get_new_peers():
# Start threads to ask the peers for more peers
threads = []
for peer in peers_we_ask:
t = Thread(target=_do_ask_peer, args=[peer], daemon=True)
t = Thread(
target=_do_ask_peer,
args=[peer], daemon=True, name='_do_ask_peer')
t.start()
threads.append(t)
peers_we_ask.clear()

View File

@ -0,0 +1,2 @@
from .streamfrom import stream_from_peers
from .streamto import stream_to_peer

View File

@ -5,6 +5,7 @@ Download blocks that are being diffused
doesn't apply for blocks in the gossip queue that are awaiting
descision to fluff or stem
"""
from ast import Index
from threading import Thread, Semaphore
from random import SystemRandom
from time import sleep
@ -13,7 +14,7 @@ from typing import TYPE_CHECKING, List
import blockdb
from ..constants import BLOCK_ID_SIZE, BLOCK_MAX_SIZE, BLOCK_MAX_SIZE_LEN, BLOCK_STREAM_OFFSET_DIGITS
from ...constants import BLOCK_ID_SIZE, BLOCK_MAX_SIZE, BLOCK_SIZE_LEN, BLOCK_STREAM_OFFSET_DIGITS
if TYPE_CHECKING:
from socket import socket
@ -24,8 +25,8 @@ from ordered_set import OrderedSet
import logger
import onionrblocks
from ..peerset import gossip_peer_set
from ..commands import GossipCommands, command_to_byte
from ...peerset import gossip_peer_set
from ...commands import GossipCommands, command_to_byte
"""
This program is free software: you can redistribute it and/or modify
@ -43,8 +44,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
MAX_STREAMS = 3
CONNECT_TIMEOUT = 12
MAX_STREAMS = 6
CONNECT_TIMEOUT = 45
MAX_TRIED_PEERS = 10_000
@ -62,8 +63,13 @@ def stream_from_peers():
def _stream_from_peer(peer: 'Peer'):
stream_counter = 0
stream_times = 100
try:
sock = peer.get_socket(CONNECT_TIMEOUT)
except ConnectionRefusedError:
need_socket_lock.release()
return
except Exception:
logger.warn(traceback.format_exc(), terminal=True)
need_socket_lock.release()
@ -75,21 +81,32 @@ def stream_from_peers():
sock.sendall(
str(offset).zfill(BLOCK_STREAM_OFFSET_DIGITS).encode('utf-8'))
while True:
while stream_times >= stream_counter:
stream_counter += 1
logger.debug("Reading block of id in stream with " + peer.transport_address, terminal=True)
sock.settimeout(5)
block_id = sock.recv(BLOCK_ID_SIZE)
if blockdb.has_block(block_id):
sock.sendall(int(0).to_bytes(1, 'big'))
continue
sock.sendall(int(1).to_bytes(1, 'big'))
block_size = int(sock.recv(BLOCK_MAX_SIZE_LEN))
#logger.debug("Reading block size in stream", terminal=True)
sock.settimeout(5)
block_size = int(sock.recv(BLOCK_SIZE_LEN))
if block_size > BLOCK_MAX_SIZE or block_size <= 0:
logger.warn(
f"Peer {peer.transport_address} " +
"reported block size out of range")
break
sock.settimeout(5)
block_data = sock.recv(block_size)
#logger.debug(
# "We got a block from stream, assuming it is valid",
# terminal=True)
try:
blockdb.add_block_to_db(
onionrblocks.Block(
@ -102,8 +119,9 @@ def stream_from_peers():
# Tell them to keep streaming
sock.sendall(int(1).to_bytes(1, 'big'))
except (BrokenPipeError, TimeoutError) as e:
logger.info(f"{e} when streaming peers", terminal=True)
logger.debug(traceback.format_exc())
pass
#logger.debug(f"{e} when streaming from peers", terminal=True)
#logger.debug(traceback.format_exc())
except Exception:
logger.warn(traceback.format_exc(), terminal=True)
finally:
@ -112,8 +130,14 @@ def stream_from_peers():
# spawn stream threads infinitely
while True:
need_socket_lock.acquire()
available_set = gossip_peer_set - tried_peers
if not len(available_set) and len(tried_peers):
try:
tried_peers.clear()
except IndexError:
pass
available_set = gossip_peer_set.copy()
peers = sys_rand.sample(
available_set,
min(MAX_STREAMS, len(available_set)))
@ -124,10 +148,13 @@ def stream_from_peers():
while len(peers):
try:
need_socket_lock.acquire()
Thread(
target=_stream_from_peer,
args=[peers.pop()],
daemon=True).start()
daemon=True,
name="_stream_from_peer").start()
except IndexError:
need_socket_lock.release()
break

View File

@ -0,0 +1,58 @@
from secrets import SystemRandom
from time import time
from typing import List, TYPE_CHECKING
#if TYPE_CHECKING:
from onionrblocks import Block
from gossip.commands import GossipCommands, command_to_byte
from blockdb import get_blocks_after_timestamp
from ...constants import BLOCK_ID_SIZE, BLOCK_SIZE_LEN
from ...peerset import gossip_peer_set
from ...server import lastincoming
SECS_ELAPSED_NO_INCOMING_BEFORE_STREAM = 3
class SendTimestamp:
timestamp: int = 0
def stream_to_peer():
if SECS_ELAPSED_NO_INCOMING_BEFORE_STREAM > time() - lastincoming.last_incoming_timestamp:
SendTimestamp.timestamp = int(time()) - 60
return
if not len(gossip_peer_set):
return
rand = SystemRandom()
peer = rand.choice(gossip_peer_set)
buffer: List['Block'] = []
def _do_upload():
print('uploading to', peer.transport_address)
with peer.get_socket(30) as p:
p.sendall(command_to_byte(GossipCommands.PUT_BLOCK_DIFFUSE))
while len(buffer):
try:
block = buffer.pop()
except IndexError:
break
p.sendall(block.id.zfill(BLOCK_ID_SIZE))
if int.from_bytes(p.recv(1), 'big') == 0:
continue
block_size = str(len(block.raw)).zfill(BLOCK_SIZE_LEN)
p.sendall(block_size.encode('utf-8'))
p.sendall(block.raw)
# Buffer some blocks so we're not streaming too many to one peer
# and to efficiently avoid connecting without sending anything
buffer_max = 10
for block in get_blocks_after_timestamp(SendTimestamp.timestamp):
assert isinstance(block, Block)
buffer.append(block)
if len(buffer) > buffer_max:
_do_upload(buffer)
if len(buffer):
_do_upload()
SendTimestamp.timestamp = int(time()) - 60

View File

@ -6,6 +6,7 @@ class GossipCommands(IntEnum):
PEER_EXCHANGE = auto()
STREAM_BLOCKS = auto()
PUT_BLOCKS = auto()
PUT_BLOCK_DIFFUSE = auto()
def command_to_byte(cmd: GossipCommands):

View File

@ -2,7 +2,7 @@ BOOTSTRAP_ATTEMPTS = 5
PEER_AMOUNT_TO_ASK = 3
TRANSPORT_SIZE_BYTES = 64
BLOCK_MAX_SIZE = 1024 * 2000
BLOCK_MAX_SIZE_LEN = len(str(BLOCK_MAX_SIZE))
BLOCK_SIZE_LEN = len(str(BLOCK_MAX_SIZE))
BLOCK_ID_SIZE = 128
BLOCK_STREAM_OFFSET_DIGITS = 8
DANDELION_EPOCH_LENGTH = 60
@ -14,4 +14,4 @@ MAX_INBOUND_DANDELION_EDGE = 50 # Mainly picked to avoid slowloris
OUTBOUND_DANDELION_EDGES = 2
MAX_STEM_BLOCKS_PER_STREAM = 1000
BLACKHOLE_EVADE_TIMER_SECS = DANDELION_EPOCH_LENGTH * 3
BLACKHOLE_EVADE_TIMER_SECS = 10

View File

@ -1,10 +1,12 @@
import asyncio
from audioop import add
import traceback
from time import time
from typing import TYPE_CHECKING
from typing import Set, Tuple
from queue import Queue
from threading import Thread
from onionrblocks import Block
from gossip import constants
from ..connectpeer import connect_peer
@ -19,10 +21,13 @@ if TYPE_CHECKING:
from asyncio import StreamReader, StreamWriter
from filepaths import gossip_server_socket_file
import blockdb
from blockdb import add_block_to_db
from ..commands import GossipCommands
from ..peerset import gossip_peer_set
from .acceptstem import accept_stem_blocks
from .diffuseblocks import diffuse_blocks
from .import lastincoming
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -53,6 +58,7 @@ def gossip_server():
break
except asyncio.IncompleteReadError:
break
lastincoming.last_incoming_timestamp = int(time())
cmd = int.from_bytes(cmd, 'big')
if cmd == b'' or cmd == 0:
@ -60,7 +66,6 @@ def gossip_server():
match GossipCommands(cmd):
case GossipCommands.PING:
writer.write(b'PONG')
break
case GossipCommands.ANNOUNCE:
async def _read_announce():
address = await reader.readuntil(b'\n')
@ -72,7 +77,8 @@ def gossip_server():
'callback': connect_peer},
threaded=True)
writer.write(int(1).to_bytes(1, 'big'))
await asyncio.wait_for(_read_announce(), 10)
await writer.drain()
await asyncio.wait_for(_read_announce(), 30)
case GossipCommands.PEER_EXCHANGE:
for peer in gossip_peer_set:
@ -92,31 +98,51 @@ def gossip_server():
reader, writer,
inbound_dandelion_edge_count)
except asyncio.exceptions.TimeoutError:
pass
logger.debug(
"Inbound edge timed out when steming blocks to us",
terminal=True)
except asyncio.exceptions.IncompleteReadError:
pass
logger.debug(
"Inbound edge timed out (Incomplete Read) when steming blocks to us",
terminal=True)
except Exception:
logger.warn(
f"Err acceptind stem blocks\n{traceback.format_exc()}",
f"Err accepting stem blocks\n{traceback.format_exc()}",
terminal=True)
# Subtract dandelion edge, make sure >=0
inbound_dandelion_edge_count[0] = \
max(inbound_dandelion_edge_count[0] - 1, 0)
break
case GossipCommands.PUT_BLOCK_DIFFUSE:
async def _get_block_diffused():
block_id = await reader.readexactly(constants.BLOCK_ID_SIZE)
if blockdb.has_block(block_id):
writer.write(int(0).to_bytes(1, 'big'))
else:
writer.write(int(1).to_bytes(1, 'big'))
await writer.drain()
block_size = int(await asyncio.wait_for(reader.readexactly(constants.BLOCK_SIZE_LEN), 30))
block_data = await reader.readexactly(block_size)
await writer.drain()
writer.close()
Thread(
target=add_block_to_db,
args=[
Block(block_id, block_data, auto_verify=True)]
).start()
await _get_block_diffused()
break
try:
await writer.drain()
except BrokenPipeError:
pass
async def main():
server = await asyncio.start_unix_server(
peer_connected, gossip_server_socket_file
)
async with server:
await server.serve_forever()

View File

@ -7,12 +7,11 @@ from onionrblocks import Block
import logger
from ..dandelion import StemAcceptResult
from ..constants import BLOCK_ID_SIZE, BLOCK_MAX_SIZE
from ..constants import BLOCK_ID_SIZE, BLOCK_SIZE_LEN, BLOCK_MAX_SIZE
from ..constants import MAX_INBOUND_DANDELION_EDGE, MAX_STEM_BLOCKS_PER_STREAM
from ..blockqueues import gossip_block_queues
block_size_digits = len(str(BLOCK_MAX_SIZE))
base_wait_timeout = 120
if TYPE_CHECKING:
@ -28,21 +27,23 @@ async def accept_stem_blocks(
writer.write(StemAcceptResult.DENY)
return
writer.write(StemAcceptResult.ALLOW)
await writer.drain()
inbound_edge_count[0] += 1
# Start getting the first block
read_routine = reader.readexactly(BLOCK_ID_SIZE)
block_queue_to_use = secrets.choice(gossip_block_queues)
for _ in range(MAX_STEM_BLOCKS_PER_STREAM):
block_id = (
await wait_for(read_routine, base_wait_timeout)).decode('utf-8')
read_routine = reader.readexactly(BLOCK_ID_SIZE)
logger.debug(f"Reading block id in stem server", terminal=True)
block_id = await wait_for(read_routine, base_wait_timeout)
block_id = block_id.decode('utf-8')
if not block_id:
break
logger.debug(f"Reading block size in stem server", terminal=True)
block_size = (await wait_for(
reader.readexactly(block_size_digits),
reader.readexactly(BLOCK_SIZE_LEN),
base_wait_timeout)).decode('utf-8')
if not block_size:
break
@ -53,6 +54,8 @@ async def accept_stem_blocks(
if block_size > BLOCK_MAX_SIZE:
raise ValueError("Max block size")
logger.debug(f"Reading block of size {block_size} in stem server", terminal=True)
raw_block: bytes = await wait_for(
reader.readexactly(block_size), base_wait_timeout * 6)
if not raw_block:
@ -66,5 +69,3 @@ async def accept_stem_blocks(
# Regardless of stem phase, we add to queue
# Client will decide if they are to be stemmed
read_routine = reader.readexactly(BLOCK_ID_SIZE)

View File

@ -6,9 +6,8 @@ doesn't apply for blocks in the gossip queue that are awaiting
descision to fluff or stem
"""
from asyncio import IncompleteReadError, wait_for
from asyncio import IncompleteReadError, wait_for, Queue, sleep
import queue
import traceback
from typing import TYPE_CHECKING
from time import time
@ -17,9 +16,11 @@ if TYPE_CHECKING:
from asyncio import StreamWriter, StreamReader
from onionrblocks import Block
from ..constants import BLOCK_MAX_SIZE, BLOCK_MAX_SIZE_LEN, BLOCK_STREAM_OFFSET_DIGITS
from ..constants import BLOCK_MAX_SIZE, BLOCK_SIZE_LEN
from ..constants import BLOCK_STREAM_OFFSET_DIGITS
import logger
import blockdb
from blockdb import get_blocks_after_timestamp, block_storage_observers
"""
This program is free software: you can redistribute it and/or modify
@ -53,13 +54,7 @@ async def diffuse_blocks(reader: 'StreamReader', writer: 'StreamWriter'):
raise ValueError(
"Peer's specified time offset skewed too far into the future")
newly_stored_blocks = queue.Queue()
def _add_to_queue(bl):
newly_stored_blocks.put_nowait(bl)
block_storage_observers.append(
_add_to_queue
)
async def _send_block(block: 'Block'):
writer.write(block.id)
@ -69,10 +64,9 @@ async def diffuse_blocks(reader: 'StreamReader', writer: 'StreamWriter'):
if int.from_bytes(await reader.readexactly(1), 'big') == 0:
return
await writer.drain()
# write block size
writer.write(
str(len(block.raw)).zfill(BLOCK_MAX_SIZE_LEN).encode('utf-8'))
str(len(block.raw)).zfill(BLOCK_SIZE_LEN).encode('utf-8'))
await writer.drain()
writer.write(block.raw)
await writer.drain()
@ -91,17 +85,23 @@ async def diffuse_blocks(reader: 'StreamReader', writer: 'StreamWriter'):
)
except IncompleteReadError:
keep_writing = False
time_offset = time()
# Diffuse blocks stored since we started this stream
while keep_writing:
await _send_block(newly_stored_blocks.get())
try:
keep_writing = bool(
int.from_bytes(await reader.readexactly(1), 'big')
)
except IncompleteReadError:
keep_writing = False
bls = blockdb.get_blocks_after_timestamp(time_offset)
await sleep(1) # Must be here to avoid blocking the event loop
for bl in bls:
await _send_block(bl)
try:
keep_writing = bool(
int.from_bytes(await reader.readexactly(1), 'big')
)
except IncompleteReadError:
keep_writing = False
break
except ConnectionResetError:
pass
except Exception:
logger.warn(traceback.format_exc(), terminal=True)
block_storage_observers.remove(_add_to_queue)

View File

@ -0,0 +1 @@
last_incoming_timestamp = 0

View File

@ -1,13 +0,0 @@
# httpapi
The httpapi contains collections of endpoints for the client and public API servers.
## Files:
configapi: manage onionr configuration from the client http api
friendsapi: add, remove and list friends from the client http api
miscpublicapi: misculanious onionr network interaction from the **public** httpapi, such as announcements, block fetching and uploading.
profilesapi: work in progress in returning a profile page for an Onionr user

View File

@ -1,36 +0,0 @@
"""
Onionr - Private P2P Communication
Register plugins flask blueprints for the client http server
"""
import onionrplugins
import config
from .fdsafehandler import FDSafeHandler
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def load_plugin_blueprints(flaskapp, blueprint: str = 'flask_blueprint'):
"""Iterate enabled plugins and load any http endpoints they have"""
config.reload()
disabled = config.get('plugins.disabled', [])
for plugin in onionrplugins.get_enabled_plugins():
if plugin in disabled:
continue
plugin = onionrplugins.get_plugin(plugin)
try:
flaskapp.register_blueprint(getattr(plugin, blueprint))
except AttributeError:
pass

View File

@ -1,43 +0,0 @@
"""Onionr - Private P2P Communication.
Serialized APIs
"""
from asyncio.log import logger
import secrets
from flask import Blueprint, Response, request
from onionrblocks import Block
import logger
from gossip import blockqueues
from gossip.constants import BLOCK_ID_SIZE
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
blockapi_blueprint = Blueprint('blockapi', __name__)
stream_to_use = secrets.randbits(1)
# Add a block that we generated (or received from a transport like LAN/sneakernet)
@blockapi_blueprint.route('/addvdfblock', methods=['POST'])
def block_serialized():
req_data = request.data
block_id = req_data[:BLOCK_ID_SIZE]
block_data = req_data[BLOCK_ID_SIZE:]
blockqueues.gossip_block_queues[stream_to_use].put(
Block(block_id, block_data, auto_verify=False))
return "ok"

View File

@ -1 +0,0 @@
from . import shutdown, setbindip

View File

@ -1,42 +0,0 @@
import gevent
from gevent import socket, sleep
import secrets, random
import config, logger
import os
# Hacky monkey patch so we can bind random localhosts without gevent trying to switch with an empty hub
socket.getfqdn = lambda n: n
def _get_acceptable_random_number()->int:
"""Return a cryptographically random number in the inclusive range (1, 255)"""
number = 0
while number == 0:
number = secrets.randbelow(0xFF)
return number
def set_bind_IP(filePath=''):
'''Set a random localhost IP to a specified file (intended for private or public API localhost IPs)'''
if config.get('general.random_bind_ip', True):
hostOctets = []
# Build the random localhost address
for i in range(3):
hostOctets.append(str(_get_acceptable_random_number()))
hostOctets = ['127'] + hostOctets
# Convert the localhost address to a normal string address
data = '.'.join(hostOctets)
# Try to bind IP. Some platforms like Mac block non normal 127.x.x.x
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((data, 0))
except OSError:
# if mac/non-bindable, show warning and default to 127.0.0.1
logger.warn('Your platform appears to not support random local host addresses 127.x.x.x. Falling back to 127.0.0.1.')
data = '127.0.0.1'
s.close()
else:
data = '127.0.0.1'
if filePath != '':
with open(filePath, 'w') as bindFile:
bindFile.write(data)
return data

View File

@ -1,30 +0,0 @@
"""Onionr - Private P2P Communication.
Shutdown the node either hard or cleanly
"""
from flask import Blueprint, Response
from flask import g
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
shutdown_bp = Blueprint('shutdown', __name__)
def shutdown(client_api_inst):
try:
client_api_inst.httpServer.stop()
except AttributeError:
pass
return Response("bye")

View File

@ -1,66 +0,0 @@
"""
Onionr - Private P2P Communication
This file handles configuration setting and getting from the HTTP API
"""
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from json import JSONDecodeError
import ujson as json
from flask import Blueprint, request, Response, abort
import config, onionrutils
from onionrutils.bytesconverter import bytes_to_str
config.reload()
config_BP = Blueprint('config_BP', __name__)
@config_BP.route('/config/get')
def get_all_config():
"""Simply return all configuration as JSON string"""
return Response(json.dumps(config.get_config(), indent=4, sort_keys=True))
@config_BP.route('/config/get/<key>')
def get_by_key(key):
"""Return a config setting by key"""
return Response(json.dumps(config.get(key)))
@config_BP.route('/config/setall', methods=['POST'])
def set_all_config():
"""Overwrite existing JSON config with new JSON string"""
try:
new_config = request.get_json(force=True)
except JSONDecodeError:
abort(400)
else:
config.set_config(new_config)
config.save()
return Response('success')
@config_BP.route('/config/set/<key>', methods=['POST'])
def set_by_key(key):
"""Overwrite/set only 1 config key"""
"""
{
'data': data
}
"""
try:
data = json.loads(bytes_to_str(request.data))
except (JSONDecodeError, KeyError):
abort(400)
config.set(key, data, True)
return Response('success')

View File

@ -1,16 +0,0 @@
from gevent.pywsgi import WSGIServer, WSGIHandler
from gevent import Timeout
class FDSafeHandler(WSGIHandler):
'''Our WSGI handler. Doesn't do much non-default except timeouts'''
def handle(self):
self.timeout = Timeout(120, Exception)
self.timeout.start()
try:
WSGIHandler.handle(self)
except Timeout as ex:
if ex is self.timeout:
pass
else:
raise

View File

@ -1,30 +0,0 @@
from os.path import exists, dirname
import ujson
from flask import Blueprint, Response, request
from utils.identifyhome import identify_home
from utils.readoffset import read_from_offset
offset_reader_api = Blueprint('offsetreaderapi', __name__)
@offset_reader_api.route('/readfileoffset/<name>')
def offset_reader_endpoint(name):
if not name[:-4].isalnum():
return Response(400, "Path must be alphanumeric except for file ext")
path = identify_home() + name
if not exists(path):
return Response(404, "Path not found in Onionr data directory")
offset = request.args.get('offset')
if not offset:
offset = 0
else:
offset = int(offset)
result = read_from_offset(path, offset)._asdict()
return ujson.dumps(result, reject_bytes=False)

View File

@ -1,75 +0,0 @@
"""Onionr - Private P2P Communication.
This file creates http endpoints for friend management
"""
import ujson as json
from onionrusers import contactmanager
from flask import Blueprint, Response, request, abort, redirect
from coredb import keydb
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
friends = Blueprint('friends', __name__)
@friends.route('/friends/list')
def list_friends():
pubkey_list = {}
friend_list = contactmanager.ContactManager.list_friends()
for friend in friend_list:
pubkey_list[friend.publicKey] = {'name': friend.get_info('name')}
return json.dumps(pubkey_list)
@friends.route('/friends/add/<pubkey>', methods=['POST'])
def add_friend(pubkey):
contactmanager.ContactManager(pubkey, saveUser=True).setTrust(1)
try:
return redirect(request.referrer + '#' + request.form['token'])
except TypeError:
return Response(
"Added, but referrer not set, cannot return to friends page")
@friends.route('/friends/remove/<pubkey>', methods=['POST'])
def remove_friend(pubkey):
contactmanager.ContactManager(pubkey).setTrust(0)
contactmanager.ContactManager(pubkey).delete_contact()
keydb.removekeys.remove_user(pubkey)
try:
return redirect(request.referrer + '#' + request.form['token'])
except TypeError:
return Response(
"Friend removed, but referrer not set, cannot return to page")
@friends.route('/friends/setinfo/<pubkey>/<key>', methods=['POST'])
def set_info(pubkey, key):
data = request.form['data']
contactmanager.ContactManager(pubkey).set_info(key, data)
try:
return redirect(request.referrer + '#' + request.form['token'])
except TypeError:
return Response(
"Info set, but referrer not set, cannot return to friends page")
@friends.route('/friends/getinfo/<pubkey>/<key>')
def get_info(pubkey, key):
ret_data = contactmanager.ContactManager(pubkey).get_info(key)
if ret_data is None:
abort(404)
else:
return ret_data

View File

@ -1,35 +0,0 @@
'''
Onionr - Private P2P Communication
Set default onionr http headers
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
FEATURE_POLICY = """vibrate; vr; webauthn; usb; sync-xhr; speaker;
picture-in-picture; payment; midi; microphone; magnetometer; gyroscope;
geolocation; fullscreen; encrypted-media; document-domain;
camera; accelerometer; ambient-light-sensor""".replace('\n', '') # have to remove \n for flask
def set_default_onionr_http_headers(flask_response):
'''Response headers'''
flask_response.headers['Content-Security-Policy'] = "default-src 'none'; style-src data: 'unsafe-inline'; img-src data:"
flask_response.headers['X-Frame-Options'] = 'deny'
flask_response.headers['X-Content-Type-Options'] = "nosniff"
flask_response.headers['Server'] = ''
flask_response.headers['Date'] = 'Thu, 1 Jan 1970 00:00:00 GMT' # Clock info is probably useful to attackers. Set to unix epoch.
flask_response.headers['Connection'] = "close"
flask_response.headers['Clear-Site-Data'] = '"cache", "cookies", "storage", "executionContexts"'
flask_response.headers['Feature-Policy'] = FEATURE_POLICY
flask_response.headers['Referrer-Policy'] = 'same-origin'
return flask_response

View File

@ -1 +0,0 @@
from . import staticfiles, endpoints

View File

@ -1,111 +0,0 @@
"""Onionr - Private P2P Communication.
Misc client API endpoints too small to need their own file and that need access to the client api inst
"""
import os
import subprocess
import platform
from sys import stdout as sys_stdout
from flask import Response, Blueprint, request, send_from_directory, abort
from flask import g
from gevent import sleep
import unpaddedbase32
from httpapi import apiutils
import onionrcrypto
import config
from onionrutils import mnemonickeys
from onionrutils import bytesconverter
import onionrvalues
from utils import reconstructhash
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
pub_key = onionrcrypto.pub_key.replace('=', '')
SCRIPT_NAME = os.path.dirname(os.path.realpath(__file__)) + \
f'/../../../{onionrvalues.SCRIPT_NAME}'
class PrivateEndpoints:
def __init__(self, client_api):
private_endpoints_bp = Blueprint('privateendpoints', __name__)
self.private_endpoints_bp = private_endpoints_bp
@private_endpoints_bp.route('/www/<path:path>', endpoint='www')
def wwwPublic(path):
if not config.get("www.private.run", True):
abort(403)
return send_from_directory(config.get('www.private.path',
'static-data/www/private/'), path)
@private_endpoints_bp.route('/getpid')
def get_pid():
return Response(str(os.getpid()))
@private_endpoints_bp.route('/isatty')
def get_is_atty():
return Response(str(sys_stdout.isatty()).lower())
@private_endpoints_bp.route('/ping')
def ping():
# Used to check if client api is working
return Response("pong!")
@private_endpoints_bp.route('/shutdown')
def shutdown():
return apiutils.shutdown.shutdown(client_api)
@private_endpoints_bp.route('/restartclean')
def restart_clean():
subprocess.Popen([SCRIPT_NAME, 'restart'])
return Response("bye")
@private_endpoints_bp.route('/getuptime')
def show_uptime():
return Response(str(client_api.getUptime()))
@private_endpoints_bp.route('/getActivePubkey')
def get_active_pubkey():
return Response(pub_key)
@private_endpoints_bp.route('/getHumanReadable')
def get_human_readable_default():
return Response(mnemonickeys.get_human_readable_ID())
@private_endpoints_bp.route('/getHumanReadable/<name>')
def get_human_readable(name):
name = unpaddedbase32.repad(bytesconverter.str_to_bytes(name))
return Response(mnemonickeys.get_human_readable_ID(name))
@private_endpoints_bp.route('/getBase32FromHumanReadable/<words>')
def get_base32_from_human_readable(words):
return Response(
bytesconverter.bytes_to_str(mnemonickeys.get_base32(words)))
@private_endpoints_bp.route('/setonboarding', methods=['POST'])
def set_onboarding():
return Response(
config.onboarding.set_config_from_onboarding(request.get_json()))
@private_endpoints_bp.route('/os')
def get_os_system():
return Response(platform.system().lower())

View File

@ -1,80 +0,0 @@
"""Onionr - Private P2P Communication
Register static file routes
"""
import os
import mimetypes
from flask import Blueprint, send_from_directory
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# Was having some mime type issues on windows, this appeared to fix it.
# we have no-sniff set, so if the mime types are invalid sripts can't load.
mimetypes.add_type('application/javascript', '.js')
mimetypes.add_type('text/css', '.css')
static_files_bp = Blueprint('staticfiles', __name__)
# should be set to onionr install directory from onionr startup
root = os.path.dirname(os.path.realpath(__file__)) + \
'/../../../static-data/www/'
@static_files_bp.route('/onboarding/', endpoint='onboardingIndex')
def onboard():
return send_from_directory(f'{root}onboarding/', "index.html")
@static_files_bp.route('/onboarding/<path:path>', endpoint='onboarding')
def onboard_files(path):
return send_from_directory(f'{root}onboarding/', path)
@static_files_bp.route('/friends/<path:path>', endpoint='friends')
def loadContacts(path):
return send_from_directory(root + 'friends/', path)
@static_files_bp.route('/friends/', endpoint='friendsindex')
def loadContacts():
return send_from_directory(root + 'friends/', 'index.html')
@static_files_bp.route('/profiles/<path:path>', endpoint='profiles')
def loadContacts(path):
return send_from_directory(root + 'profiles/', path)
@static_files_bp.route('/profiles/', endpoint='profilesindex')
def loadContacts():
return send_from_directory(root + 'profiles/', 'index.html')
@static_files_bp.route('/shared/<path:path>', endpoint='sharedContent')
def sharedContent(path):
return send_from_directory(root + 'shared/', path)
@static_files_bp.route('/', endpoint='onionrhome')
def hello():
# ui home
return send_from_directory(root + 'private/', 'index.html')
@static_files_bp.route('/private/<path:path>', endpoint='homedata')
def homedata(path):
return send_from_directory(root + 'private/', path)

View File

@ -1 +0,0 @@
from . import client

View File

@ -1,102 +0,0 @@
"""Onionr - Private P2P Communication.
Process incoming requests to the client api server to validate
that they are legitimate and not DNSR/XSRF or other local adversary
"""
from ipaddress import ip_address
import hmac
from flask import Blueprint, request, abort, g
from httpapi import httpheaders
from . import pluginwhitelist
import config
import logger
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
# Be extremely mindful of this.
# These are endpoints available without a password
whitelist_endpoints = [
'www', 'staticfiles.homedata',
'staticfiles.sharedContent',
'staticfiles.friends', 'staticfiles.friendsindex', 'siteapi.site',
'siteapi.siteFile', 'staticfiles.onionrhome',
'themes.getTheme', 'staticfiles.onboarding', 'staticfiles.onboardingIndex']
remote_safe_whitelist = ['www', 'staticfiles']
public_remote_enabled = config.get('ui.public_remote_enabled', False)
public_remote_hostnames = config.get('ui.public_remote_hosts', [])
class ClientAPISecurity:
def __init__(self, client_api):
client_api_security_bp = Blueprint('clientapisecurity', __name__)
self.client_api_security_bp = client_api_security_bp
self.client_api = client_api
pluginwhitelist.load_plugin_security_whitelist_endpoints(
whitelist_endpoints)
@client_api_security_bp.before_app_request
def validate_request():
"""Validate request has set password & is the correct hostname."""
# For the purpose of preventing DNS rebinding attacks
if ip_address(client_api.host).is_loopback:
localhost = True
if request.host != '%s:%s' % \
(client_api.host, client_api.bindPort):
localhost = False
if not localhost and public_remote_enabled:
if request.host not in public_remote_hostnames:
logger.warn(
f'{request.host} not in {public_remote_hostnames}')
abort(403)
else:
if not localhost:
logger.warn(
f'Possible DNS rebinding attack by {request.host}')
abort(403)
# Static files for Onionr sites
if request.path.startswith('/site/'):
return
if request.endpoint in whitelist_endpoints:
return
try:
if not hmac.compare_digest(
request.headers['token'], client_api.clientToken):
if not hmac.compare_digest(
request.form['token'], client_api.clientToken):
abort(403)
except KeyError:
if not hmac.compare_digest(
request.form['token'], client_api.clientToken):
abort(403)
@client_api_security_bp.after_app_request
def after_req(resp):
# Security headers
resp = httpheaders.set_default_onionr_http_headers(resp)
if request.endpoint in ('siteapi.site', 'siteapi.siteFile'):
resp.headers['Content-Security-Policy'] = \
"default-src 'none'; style-src 'self' data: 'unsafe-inline'; img-src 'self' data:; media-src 'self' data:" # noqa
else:
resp.headers['Content-Security-Policy'] = \
"default-src 'none'; script-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; media-src 'self'; frame-src 'none'; font-src 'self'; connect-src 'self'" # noqa
return resp

View File

@ -1,67 +0,0 @@
"""Onionr - Private P2P Communication.
Process incoming requests to the public api server for certain attacks
"""
from flask import Blueprint, request, abort, g
from httpapi import httpheaders
from onionrutils import epoch
from lan import getip
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
class LANAPISecurity:
def __init__(self, lan_client):
lan_api_security_bp = Blueprint('lanapisecurity', __name__)
self.lan_api_security_bp = lan_api_security_bp
@lan_api_security_bp.before_app_request
def validate_request():
"""Validate request has the correct hostname"""
# If high security level, deny requests to public
# (HS should be disabled anyway for Tor, but might not be for I2P)
transports = getip.lan_ips
if lan_client.config.get('general.security_level', default=1) > 0:
abort(403)
if request.host not in transports:
# Abort conn if wrong HTTP hostname, to prevent DNS rebinding
abort(403)
lan_client.hitCount += 1 # raise hit count for valid requests
try:
if 'onionr' in request.headers['User-Agent'].lower():
g.is_onionr_client = True
else:
g.is_onionr_client = False
except KeyError:
g.is_onionr_client = False
@lan_api_security_bp.after_app_request
def send_headers(resp):
"""Send api, access control headers"""
resp = httpheaders.set_default_onionr_http_headers(resp)
# Network API version
resp.headers['X-API'] = lan_client.API_VERSION
# Delete some HTTP headers for Onionr user agents
NON_NETWORK_HEADERS = ('Content-Security-Policy', 'X-Frame-Options',
'X-Content-Type-Options', 'Feature-Policy',
'Clear-Site-Data', 'Referrer-Policy')
try:
if g.is_onionr_client:
for header in NON_NETWORK_HEADERS:
del resp.headers[header]
except AttributeError:
abort(403)
lan_client.lastRequest = epoch.get_rounded_epoch(roundS=5)
return resp

View File

@ -1,33 +0,0 @@
"""Onionr - Private P2P Communication.
Load web UI client endpoints into the whitelist from plugins
"""
import onionrplugins
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def load_plugin_security_whitelist_endpoints(whitelist: list):
"""Accept a list reference of whitelist endpoints from security/client.py and
append plugin's specified endpoints to them by attribute"""
for plugin in onionrplugins.get_enabled_plugins():
try:
plugin = onionrplugins.get_plugin(plugin)
except FileNotFoundError:
continue
try:
whitelist.extend(getattr(plugin, "security_whitelist"))
except AttributeError:
pass

View File

@ -1,6 +0,0 @@
# sse
This folder contains a wrapper for handling server sent event loops

View File

@ -1,18 +0,0 @@
"""Onionr - Private P2P Communication.
server sent event modules, incl a wrapper and endpoints for client + public api
"""
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

View File

@ -1,41 +0,0 @@
"""Onionr - Private P2P Communication.
SSE API for node client access
"""
from pathlib import Path
from flask import g, Blueprint
from gevent import sleep
import gevent
import ujson
from onionrutils.epoch import get_epoch
from .. import wrapper
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
private_sse_blueprint = Blueprint('privatesse', __name__)
SSEWrapper = wrapper.SSEWrapper()
gevent.hub.Hub.NOT_ERROR = (gevent.GreenletExit, SystemExit, Exception)
@private_sse_blueprint.route('/hello')
def stream_hello():
def print_hello():
while True:
yield "hello\n\n"
sleep(1)
return SSEWrapper.handle_sse_request(print_hello)

View File

@ -1,34 +0,0 @@
"""Onionr - Private P2P Communication.
wrapper for server sent event endpoints
"""
from typing import Callable
from flask import Response
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
class SSEWrapper:
def __init__(self):
self.active_count: int = 0
def handle_sse_request(self, handler: Callable):
self.active_count += 1
resp = Response(handler())
resp.content_type = "text/event-stream"
self.active_count -= 1
return resp

View File

@ -1,46 +0,0 @@
"""
Onionr - Private P2P Communication
API to get current CSS theme for the client web UI
"""
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from flask import Blueprint, Response
import config
from utils import readstatic
theme_blueprint = Blueprint('themes', __name__)
LIGHT_THEME_FILES = ['bulma-light.min.css', 'styles-light.css']
DARK_THEME_FILES = ['bulma-dark.min.css', 'styles-dark.css']
def _load_from_files(file_list: list)->str:
"""Loads multiple static dir files and returns them in combined string format (non-binary)"""
combo_data = ''
for f in file_list:
combo_data += readstatic.read_static('www/shared/main/themes/' + f)
return combo_data
@theme_blueprint.route('/gettheme', endpoint='getTheme')
def get_theme_file()->Response:
"""Returns the css theme data"""
css: str
theme = config.get('ui.theme', 'dark').lower()
if theme == 'dark':
css = _load_from_files(DARK_THEME_FILES)
elif theme == 'light':
css = _load_from_files(LIGHT_THEME_FILES)
return Response(css, mimetype='text/css')

View File

@ -1,55 +0,0 @@
"""Onionr - Private P2P Communication.
Desktop notification wrapper
"""
from subprocess import Popen
try:
import simplenotifications as simplenotify
except ImportError:
notifications_enabled = False
else:
notifications_enabled = True
from utils.readstatic import get_static_dir
import config
from onionrplugins.onionrevents import event as plugin_api_event
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
if not config.get('general.show_notifications', True):
notifications_enabled = False
notification_sound_file = get_static_dir() + "sounds/notification1.mp3"
def notify(title: str = "Onionr", message: str = ""):
"""Cross platform method to show a notification."""
if not notifications_enabled:
return
plugin_api_event("notification", data={"title": title, "message": message})
simplenotify.notify(title, message)
def notification_with_sound(sound='', **kwargs):
if not notifications_enabled:
return
if not sound:
sound = notification_sound_file
try:
Popen(["mpv", sound])
except FileNotFoundError:
pass
notify(**kwargs)

View File

@ -3,38 +3,32 @@
launch the api servers and communicator
"""
import os
import queue
from time import sleep
import sys
import platform
import signal
from threading import Thread
from stem.connection import IncorrectPassword
import toomanyobjs
import filenuke
from deadsimplekv import DeadSimpleKV
import psutil
from ordered_set import OrderedSet
import config
import apiservers
import logger
from onionrplugins import onionrevents as events
from onionrutils import localcommand
from utils import identifyhome
import filepaths
import onionrvalues
from onionrutils import cleanup
from onionrcrypto import getourkeypair
import runtests
from onionrthreads import add_onionr_thread
from blockdb.blockcleaner import clean_block_database
from .. import version
from .killdaemon import kill_daemon # noqa
from .showlogo import show_logo
import gossip
from setupkvvars import setup_kv
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -51,11 +45,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def _proper_shutdown():
localcommand.local_command('shutdown')
sys.exit(1)
def _show_info_messages():
version.version(verbosity=5, function=logger.info)
logger.debug('Python version %s' % platform.python_version())
@ -72,36 +61,13 @@ def daemon():
"""Start Onionr's primary threads for communicator, API server, node, and LAN."""
def _handle_sig_term(signum, frame):
pid = str(os.getpid())
main_pid = localcommand.local_command('/getpid')
#logger.info(main_pid, terminal=True)
if main_pid and main_pid == pid:
logger.info(
f"Received sigterm, shutting down gracefully. PID: {pid}", terminal=True)
localcommand.local_command('/shutdown')
else:
logger.info(
f"Recieved sigterm in child process or fork, exiting. PID: {pid}")
sys.exit(0)
sys.exit(0)
with open(filepaths.pid_file, 'w') as f:
f.write(str(os.getpid()))
signal.signal(signal.SIGTERM, _handle_sig_term)
# Create shared objects
shared_state = toomanyobjs.TooMany()
# Add DeadSimpleKV for quasi-global variables (ephemeral key-value)
shared_state.get(DeadSimpleKV)
# Initialize the quasi-global variables
setup_kv(shared_state.get(DeadSimpleKV))
# Init run time tester
# (ensures Onionr is running right, for testing purposes)
# Run time tests are not normally run
shared_state.get(runtests.OnionrRunTestManager)
shared_state.share_object() # share the parent object to the threads
show_logo()
@ -113,15 +79,20 @@ def daemon():
events.event('init', threaded=False)
events.event('daemon_start')
Thread(target=gossip.start_gossip_threads, daemon=True).start()
add_onionr_thread(
clean_block_database, 60, 'clean_block_database', initial_sleep=0)
Thread(
target=gossip.start_gossip_threads,
daemon=True,
name='start_gossip_threads').start()
try:
apiservers.private_api.start()
events.event('shutdown', threaded=False)
while True:
sleep(60)
except KeyboardInterrupt:
pass
cleanup.delete_run_files()
if security_level >= 2:
filenuke.nuke.clean_tree(identifyhome.identify_home())

View File

@ -2,15 +2,11 @@
Gracefully stop Onionr daemon
"""
import sqlite3
import os
from signal import SIGTERM
from gevent import spawn
from onionrplugins import events
from onionrutils import localcommand
from filepaths import pid_file
import logger
import config
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -29,27 +25,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
def kill_daemon():
"""Shutdown the Onionr daemon (communicator)."""
config.reload()
try:
with open(pid_file, 'r') as pid:
os.kill(int(pid.read()), SIGTERM)
except FileNotFoundError:
logger.error("Daemon not running/pid file missing")
logger.warn('Stopping the running daemon, if one exists...', timestamp=False,
terminal=True)
# On platforms where we can, fork out to prevent locking
try:
pid = os.fork()
if pid != 0:
return
except (AttributeError, OSError):
pass
events.event('daemon_stop')
try:
spawn(
localcommand.local_command,
'/shutdown'
).get(timeout=5)
except sqlite3.OperationalError:
pass
kill_daemon.onionr_help = "Gracefully stops the " # type: ignore
kill_daemon.onionr_help += "Onionr API servers" # type: ignore

View File

@ -8,8 +8,7 @@ QUOTES = [
""),
("Study after study has show that human behavior changes when we know were being watched.\nUnder observation, we act less free, which means we effectively *are* less free.",
"Edward Snowdwen"),
("A revolution without dancing is a revolution not worth having",
"V for Vendetta"),
("Privacy is a fundamental right", ""),
("There can be no justice so long as laws are absolute. Even life itself is an exercise in exceptions",
"Picard"),
("Openness and participation are antidotes to surveillance and control",

View File

@ -1,59 +0,0 @@
"""Onionr - Private P2P Communication.
Open the web interface properly into a web browser
"""
import webbrowser
from time import sleep
import logger
from onionrutils import getclientapiserver
import config
from onionrutils.localcommand import local_command
from .daemonlaunch import geturl
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def get_url() -> str:
"""Build UI URL string and return it."""
return geturl.get_url(config)
get_url.onionr_help = "Shows the Onionr " # type: ignore
get_url.onionr_help += "web interface URL with API key" # type: ignore
def open_home():
"""Command to open web interface URL in default browser."""
try:
url = getclientapiserver.get_client_API_server()
except FileNotFoundError:
logger.error(
'Onionr seems to not be running (could not get api host)',
terminal=True)
else:
sleep(3) # Sleep a little to wait for web UI to init some vars it needs
url = get_url()
logger.info(
'If Onionr does not open automatically, use this URL: ' + url,
terminal=True)
webbrowser.open_new_tab(url)
open_home.onionr_help = "Opens the Onionr UI in the default " # type: ignore
open_home.onionr_help += "browser. Node must be running." # type: ignore

View File

@ -5,12 +5,8 @@ Sets CLI arguments for Onionr
from typing import Callable
from .. import onionrstatistics, version, daemonlaunch
from .. import openwebinterface
from .. import pubkeymanager # commands to add or change id
from .. import resetplugins # command to reinstall default plugins
from .. import softreset # command to delete onionr blocks
from .. import restartonionr # command to restart Onionr
from .. import runtimetestcmd # cmd to execute the runtime integration tests
import onionrexceptions
@ -42,15 +38,9 @@ def get_arguments() -> dict:
('version',): version.version,
('start', 'daemon'): daemonlaunch.start,
('stop', 'kill'): daemonlaunch.kill_daemon,
('restart',): restartonionr.restart,
('openhome', 'gui', 'openweb',
'open-home', 'open-web'): openwebinterface.open_home,
('get-url', 'url', 'get-web'): openwebinterface.get_url,
('addid', 'add-id'): pubkeymanager.add_ID,
('changeid', 'change-id'): pubkeymanager.change_ID,
('resetplugins', 'reset-plugins'): resetplugins.reset,
('soft-reset', 'softreset'): softreset.soft_reset,
('runtime-test', 'runtimetest'): runtimetestcmd.do_runtime_test
('resetplugins', 'reset-plugins'): resetplugins.reset
}
return args

View File

@ -1,78 +0,0 @@
"""Onionr - Private P2P Communication.
Command to restart Onionr
"""
from threading import local
import time
import os
import subprocess # nosec
from psutil import Process
import onionrvalues
from onionrutils import cleanup
from onionrutils import localcommand
import logger
import filepaths
from . import daemonlaunch
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
DEVNULL = subprocess.DEVNULL
SCRIPT_NAME = os.path.dirname(os.path.realpath(
__file__)) + f'/../../{onionrvalues.SCRIPT_NAME}'
def restart():
"""Tell the Onionr daemon to restart."""
logger.info('Restarting Onionr', terminal=True)
daemon_terminal = localcommand.local_command("getpid")
terminal = None
if daemon_terminal:
terminal = Process(int(daemon_terminal)).terminal()
else:
terminal = Process().terminal()
# On platforms where we can, fork out to prevent locking
try:
pid = os.fork()
if pid != 0:
return
except (AttributeError, OSError):
logger.warn('Could not fork on restart')
with open(filepaths.restarting_indicator, 'w') as f:
f.write('t')
daemonlaunch.kill_daemon()
while localcommand.local_command('ping', max_wait=8) == 'pong!':
time.sleep(0.3)
time.sleep(15)
while (os.path.exists(filepaths.private_API_host_file) or
(os.path.exists(filepaths.daemon_mark_file))):
time.sleep(1)
cleanup.delete_run_files()
with open(terminal, 'ab') as term:
subprocess.Popen(
[SCRIPT_NAME, 'start'],
stdout=term,
stdin=term,
stderr=term)
restart.onionr_help = 'Gracefully restart Onionr' # type: ignore

View File

@ -1,37 +0,0 @@
"""Onionr - Private P2P Communication.
Command to tell daemon to do run time tests
"""
from gevent import spawn
from onionrutils import localcommand
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def do_runtime_test():
"""Send runtime test daemon queue command."""
spawn(
localcommand.local_command,
f'daemon-event/test_runtime',
post=True,
is_json=True,
post_data={},
max_wait=300
).get(300)
do_runtime_test.onionr_help = "If Onionr is running, " # type: ignore
do_runtime_test.onionr_help += "run runtime tests (check logs)" # type: ignore

View File

@ -1,58 +0,0 @@
"""Onionr - Private P2P Communication.
Command to soft-reset Onionr (deletes blocks)
"""
import os
import shutil
from onionrutils import localcommand
from coredb import dbfiles
import filepaths
from onionrplugins import onionrevents
import logger
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def _ignore_not_found_delete(path):
try:
os.remove(path)
except FileNotFoundError:
pass
def soft_reset():
"""Command to soft reset Onionr home data.
Onionr must not be running
"""
if localcommand.local_command('/ping') == 'pong!':
logger.warn('Cannot soft reset while Onionr is running',
terminal=True)
return
path = filepaths.block_data_location
shutil.rmtree(path)
_ignore_not_found_delete(dbfiles.block_meta_db)
_ignore_not_found_delete(filepaths.upload_list)
_ignore_not_found_delete(filepaths.usage_file)
onionrevents.event('softreset')
logger.info("Soft reset Onionr", terminal=True)
soft_reset.onionr_help = "Deletes Onionr blocks and their " # type: ignore
soft_reset.onionr_help += "associated metadata, except for " # type: ignore
soft_reset.onionr_help += "any exported block files. Does NOT " # type: ignore
soft_reset.onionr_help += "delete data on " # type: ignore
soft_reset.onionr_help += "other nodes in the network." # type: ignore

View File

@ -1,6 +1,5 @@
from . import safecompare, replayvalidation
from . import safecompare
from . import getpubfrompriv
replay_validator = replayvalidation.replay_timestamp_validation
safe_compare = safecompare.safe_compare
get_pub_key_from_priv = getpubfrompriv.get_pub_key_from_priv

View File

@ -1,3 +0,0 @@
from onionrutils import epoch
def replay_timestamp_validation(timestamp):
return epoch.get_epoch() - int(timestamp) <= 2419200

View File

@ -1,4 +1,6 @@
import hmac
def safe_compare(one, two):
# Do encode here to avoid spawning core
try:
@ -9,4 +11,5 @@ def safe_compare(one, two):
two = two.encode()
except AttributeError:
pass
return hmac.compare_digest(one, two)
return hmac.compare_digest(one, two)

View File

@ -1,38 +0,0 @@
from .. import hashers
import config, onionrproofs, logger
import onionrexceptions
def verify_POW(blockContent):
'''
Verifies the proof of work associated with a block
'''
retData = False
dataLen = len(blockContent)
try:
blockContent = blockContent.encode()
except AttributeError:
pass
blockHash = hashers.sha3_hash(blockContent)
try:
blockHash = blockHash.decode() # bytes on some versions for some reason
except AttributeError:
pass
difficulty = onionrproofs.getDifficultyForNewBlock(blockContent)
if difficulty < int(config.get('general.minimum_block_pow')):
difficulty = int(config.get('general.minimum_block_pow'))
mainHash = '0000000000000000000000000000000000000000000000000000000000000000'#nacl.hash.blake2b(nacl.utils.random()).decode()
puzzle = mainHash[:difficulty]
if blockHash[:difficulty] == puzzle:
# logger.debug('Validated block pow')
retData = True
else:
logger.debug(f"Invalid token, bad proof for {blockHash} {puzzle}")
raise onionrexceptions.InvalidProof('Proof for %s needs to be %s' % (blockHash, puzzle))
return retData

View File

@ -1,22 +1,22 @@
'''
Onionr - Private P2P Communication
"""
Onionr - Private P2P Communication.
This file deals with management of modules/plugins.
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
<anagement of modules/plugins.
"""
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os, re, importlib
import traceback
@ -32,9 +32,9 @@ _instances = dict()
config.reload()
def reload(stop_event = True):
'''
"""
Reloads all the plugins
'''
"""
check()
@ -93,9 +93,9 @@ def enable(name, start_event = True):
def disable(name, stop_event = True):
'''
"""
Disables a plugin
'''
"""
check()
@ -111,9 +111,9 @@ def disable(name, stop_event = True):
stop(name)
def start(name):
'''
"""
Starts the plugin
'''
"""
check()
@ -135,9 +135,9 @@ def start(name):
return None
def stop(name):
'''
"""
Stops the plugin
'''
"""
check()
@ -183,12 +183,11 @@ def import_module_from_file(full_path_to_module):
return module
def get_plugin(name):
'''
"""
Returns the instance of a module
'''
"""
check()
if str(name).lower() in _instances:
return _instances[str(name).lower()]
else:
@ -196,23 +195,23 @@ def get_plugin(name):
return get_plugin(name)
def get_plugins():
'''
"""
Returns a list of plugins (deprecated)
'''
"""
return _instances
def exists(name):
'''
"""
Return value indicates whether or not the plugin exists
'''
"""
return os.path.isdir(get_plugins_folder(str(name).lower()))
def get_enabled_plugins():
'''
"""
Returns a list of the enabled plugins
'''
"""
check()
config.reload()
@ -220,16 +219,16 @@ def get_enabled_plugins():
return list(config.get('plugins.enabled', list()))
def is_enabled(name):
'''
"""
Return value indicates whether or not the plugin is enabled
'''
"""
return name in get_enabled_plugins()
def get_plugins_folder(name = None, absolute = True):
'''
"""
Returns the path to the plugins folder
'''
"""
path = ''
@ -246,16 +245,16 @@ def get_plugins_folder(name = None, absolute = True):
return path + '/'
def get_plugin_data_folder(name, absolute = True):
'''
"""
Returns the location of a plugin's data folder
'''
"""
return get_plugins_folder(name, absolute)
def check():
'''
"""
Checks to make sure files exist
'''
"""
if not config.is_set('plugins'):
logger.debug('Generating plugin configuration data...')

View File

@ -40,11 +40,10 @@ def __event_caller(event_name, data = {}):
if plugin in disabled: continue
try:
call(plugins.get_plugin(plugin), event_name, data, get_pluginapi(data))
except ModuleNotFoundError as e:
except ModuleNotFoundError as _:
logger.warn('Disabling nonexistant plugin "%s"...' % plugin, terminal=True)
plugins.disable(plugin, stop_event = False)
except Exception as e:
except Exception as _:
logger.error('Event "%s" failed for plugin "%s".' % (event_name, plugin), terminal=True)
logger.error('\n' + traceback.format_exc(), terminal=True)
@ -52,7 +51,7 @@ def event(event_name, data = {}, threaded = True):
"""Call an event on all plugins (if defined)"""
if threaded:
thread = Thread(target = __event_caller, args = (event_name, data))
thread = Thread(target = __event_caller, args = (event_name, data), name=f'{event_name} event', daemon=True)
thread.start()
return thread
else:

View File

@ -19,7 +19,7 @@
'''
import onionrplugins, logger
from onionrutils import localcommand
class PluginAPI:
def __init__(self, pluginapi):

View File

@ -1,21 +0,0 @@
from audioop import mul
import multiprocessing
def run_func_in_new_process(func, *args, **kwargs):
queue = multiprocessing.Queue()
def _wrap_func():
if args and kwargs:
queue.put(func(*args, **kwargs))
elif args:
queue.put(func(*args))
elif kwargs:
queue.put(func(**kwargs))
else:
queue.put(func())
proc = multiprocessing.Process(target=_wrap_func, daemon=True)
proc.start()
return queue.get()

View File

@ -10,7 +10,6 @@ import ujson as json
import config
import logger
import onionrvalues
from onionrutils import getopenport
from logger.settings import *
from utils import readstatic
"""
@ -77,14 +76,3 @@ def setup_config():
set_level(map[verbosity])
else:
logger.warn('Verbosity level %s is not valid, using default verbosity.' % verbosity)
if type(config.get('client.webpassword')) is type(None):
config.set('client.webpassword', base64.b16encode(os.urandom(32)).decode('utf-8'), savefile=True)
if type(config.get('client.client.port')) is type(None):
randomPort = getopenport.get_open_port()
config.set('client.client.port', randomPort, savefile=True)
if type(config.get('client.public.port')) is type(None):
randomPort = getopenport.get_open_port()
config.set('client.public.port', randomPort, savefile=True)
if type(config.get('client.api_version')) is type(None):
config.set('client.api_version', onionrvalues.API_VERSION, savefile=True)

View File

@ -27,7 +27,8 @@ def _onionr_thread(func: Callable,
def add_onionr_thread(
func: Callable,
sleep_secs: int, *args, initial_sleep: int = 5, **kwargs):
sleep_secs: int, thread_name: str,
*args, initial_sleep: int = 5, **kwargs):
"""Spawn a new onionr thread that exits when the main thread does.
Runs in an infinite loop with sleep between calls
@ -40,6 +41,7 @@ def add_onionr_thread(
initial_sleep,
*args),
kwargs=kwargs,
name=thread_name,
daemon=True).start()

View File

@ -37,3 +37,5 @@ def delete_run_files():
_safe_remove(filepaths.private_API_host_file)
_safe_remove(filepaths.daemon_mark_file)
_safe_remove(filepaths.lock_file)
_safe_remove(filepaths.gossip_server_socket_file)
_safe_remove(filepaths.pid_file)

View File

@ -1 +0,0 @@
from urllib3.contrib.socks import SOCKSProxyManager # noqa

View File

@ -1,37 +0,0 @@
'''
Onionr - Private P2P Communication
Return the client api server address and port, which is usually random
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import filepaths
import config
def get_client_API_server():
config.reload()
retData = ''
getconf = lambda: config.get('client.client.port')
port = getconf()
if port is None:
config.reload()
port = getconf()
try:
with open(filepaths.private_API_host_file, 'r') as host:
hostname = host.read()
except FileNotFoundError:
raise FileNotFoundError
else:
retData += '%s:%s' % (hostname, port)
return retData

View File

@ -1,29 +0,0 @@
'''
Onionr - Private P2P Communication
get an open port
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import socket
def get_open_port():
# taken from (but modified) https://stackoverflow.com/a/2838309 by https://stackoverflow.com/users/133374/albert ccy-by-sa-3 https://creativecommons.org/licenses/by-sa/3.0/
# changes from source: import moved to top of file, bind specifically to localhost
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1",0))
s.listen(1)
port = s.getsockname()[1]
s.close()
return port

View File

@ -1,103 +0,0 @@
"""Onionr - Private P2P Communication.
send a command to the local API server
"""
import urllib
import time
import requests
import deadsimplekv
import logger
import config
from . import getclientapiserver
import filepaths
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
config.reload()
cache = deadsimplekv.DeadSimpleKV(filepaths.cached_storage,
refresh_seconds=1000)
def get_hostname():
hostname = ''
waited = 0
max_wait = 3
while True:
if cache.get('client_api') is None:
try:
hostname = getclientapiserver.get_client_API_server()
except FileNotFoundError:
hostname = False
else:
cache.put('hostname', hostname)
cache.flush()
else:
hostname = cache.get('hostname')
if hostname == '' or hostname is None:
time.sleep(1)
if waited == max_wait:
return False
else:
return hostname
def local_command(command, data='', silent=True, post=False,
post_data={}, max_wait=20,
is_json=False
):
"""Send a command to the local http API server, securely.
Intended for local clients, DO NOT USE for remote peers."""
hostname = get_hostname()
# if the api host cannot be reached, return False
if not hostname:
return False
if data:
data = '&data=' + urllib.parse.quote_plus(data)
payload = 'http://%s/%s%s' % (hostname, command, data)
if not config.get('client.webpassword'):
config.reload()
try:
if post:
if is_json:
ret_data = requests.post(
payload,
json=post_data,
headers={'token': config.get('client.webpassword'),
'Connection': 'close'},
timeout=(max_wait, max_wait)).text
else:
ret_data = requests.post(
payload,
data=post_data,
headers={'token': config.get('client.webpassword'),
'Connection': 'close'},
timeout=(max_wait, max_wait)).text
else:
ret_data = requests.get(payload,
headers={'token':
config.get('client.webpassword'),
'Connection': 'close'},
timeout=(max_wait, max_wait)).text
except Exception as error:
if not silent:
logger.error('Failed to make local request (command: %s):%s' %
(command, error), terminal=True)
ret_data = False
return ret_data

View File

@ -1,49 +1,30 @@
'''
Onionr - Private P2P Communication
"""
Onionr - Private P2P Communication
validate various string data types
'''
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
import base64, string
validate various string data types
"""
import base64
import string
import unpaddedbase32, nacl.signing, nacl.encoding
from onionrutils import bytesconverter
def validate_hash(data, length=64):
'''
Validate if a string is a valid hash hex digest (does not compare, just checks length and charset)
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Length is only invalid if its *more* than the specified
'''
retVal = True
if data == False or data == True:
return False
data = data.strip()
if len(data) > length:
retVal = False
else:
try:
int(data, 16)
except ValueError:
retVal = False
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
return retVal
def validate_pub_key(key):
'''
Validate if a string is a valid base32 encoded Ed25519 key
'''
"""Validate if a string is a valid base32 encoded Ed25519 key"""
if type(key) is type(None):
return False
# Accept keys that have no = padding
@ -54,18 +35,8 @@ def validate_pub_key(key):
nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
except nacl.exceptions.ValueError:
pass
except base64.binascii.Error as err:
except base64.binascii.Error as _:
pass
else:
retVal = True
return retVal
def is_integer_string(data):
'''Check if a string is a valid base10 integer (also returns true if already an int)'''
try:
int(data)
except (ValueError, TypeError) as e:
return False
else:
return True

View File

@ -1,7 +0,0 @@
import notifier
def update_event(bl):
"""Show update notification if available, return bool of if update happened"""
if not bl.isSigner(onionrvalues.UPDATE_SIGN_KEY): raise onionrexceptions.InvalidUpdate
onionr.notifier.notify(message="A new Onionr update is available. Stay updated to remain secure.")

View File

@ -1,127 +0,0 @@
"""Onionr - Private P2P Communication.
validate new block's metadata
"""
from json import JSONDecodeError
import ujson as json
import logger, onionrexceptions
import onionrvalues
from . import stringvalidators, epoch, bytesconverter
import config, filepaths, onionrcrypto
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def validate_metadata(metadata, block_data) -> bool:
"""Validate metadata meets onionr spec (does not validate proof value computation), take in either dictionary or json string"""
ret_data = False
max_clock_difference = onionrvalues.MAX_BLOCK_CLOCK_SKEW
# convert to dict if it is json string
if type(metadata) is str:
try:
metadata = json.loads(metadata)
except JSONDecodeError:
pass
# Validate metadata dict for invalid keys to sizes that are too large
maxAge = onionrvalues.DEFAULT_EXPIRE
if type(metadata) is dict:
for i in metadata:
try:
onionrvalues.BLOCK_METADATA_LENGTHS[i]
except KeyError:
logger.warn('Block has invalid metadata key ' + i)
break
else:
testData = metadata[i]
try:
testData = len(testData)
except (TypeError, AttributeError) as e:
testData = len(str(testData))
if onionrvalues.BLOCK_METADATA_LENGTHS[i] < testData:
logger.warn('Block metadata key ' + i + ' exceeded maximum size')
break
if i == 'time':
if not stringvalidators.is_integer_string(metadata[i]):
logger.warn('Block metadata time stamp is not integer string or int')
break
isFuture = (metadata[i] - epoch.get_epoch())
if isFuture > max_clock_difference:
logger.warn('Block timestamp is skewed to the future over the max %s: %s', (max_clock_difference, isFuture))
break
if (epoch.get_epoch() - metadata[i]) > maxAge:
logger.warn('Block is outdated: %s' % (metadata[i],))
break
elif i == 'expire':
try:
if not int(metadata[i]) > epoch.get_epoch(): raise ValueError
except ValueError:
logger.warn('Block is expired: %s less than %s' % (metadata[i], epoch.get_epoch()))
break
elif i == 'encryptType':
try:
if not metadata[i] in ('asym', 'sym', ''): raise ValueError
except ValueError:
logger.warn('Invalid encryption mode')
break
elif i == 'sig':
try:
metadata['encryptType']
except KeyError:
signer = metadata['signer']
sig = metadata['sig']
encodedMeta = bytesconverter.str_to_bytes(metadata['meta'])
encodedBlock = bytesconverter.str_to_bytes(block_data)
if not onionrcrypto.signing.ed_verify(encodedMeta + encodedBlock[1:], signer, sig):
logger.warn(f'Block was signed by {signer}, but signature failed')
break
else:
# if metadata loop gets no errors, it does not break, therefore metadata is valid
# make sure we do not have another block with the same data content (prevent data duplication and replay attacks)
# Make sure time is set (validity was checked above if it is)
if not config.get('general.store_plaintext_blocks', True):
try:
if not metadata['encryptType']:
raise onionrexceptions.PlaintextNotSupported
except KeyError:
raise onionrexceptions.PlaintextNotSupported
try:
metadata['time']
except KeyError:
logger.warn("Time header not set")
return False
nonce = bytesconverter.bytes_to_str(onionrcrypto.hashers.sha3_hash(block_data))
try:
with open(filepaths.data_nonce_file, 'r') as nonceFile:
if nonce in nonceFile.read():
# we've seen that nonce before, so we can't pass metadata
raise onionrexceptions.DataExists
except FileNotFoundError:
ret_data = True
except onionrexceptions.DataExists:
# do not set ret_data to True, because data has been seen before
logger.warn(f'{nonce} seen before')
raise onionrexceptions.DataExists
else:
ret_data = True
else:
logger.warn('In call to utils.validateMetadata, metadata must be JSON string or a dictionary object')
return ret_data

View File

@ -23,54 +23,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
DENIABLE_PEER_ADDRESS = "OVPCZLOXD6DC5JHX4EQ3PSOGAZ3T24F75HQLIUZSDSMYPEOXCPFA"
PASSWORD_LENGTH = 25
ONIONR_TAGLINE = 'Private P2P Communication - GPLv3 - https://Onionr.net'
ONIONR_VERSION = '8.0.2'
ONIONR_VERSION = '9.0.0'
ONIONR_VERSION_CODENAME = 'Taraxacum'
ONIONR_VERSION_TUPLE = tuple(ONIONR_VERSION.split('.')) # (MAJOR, MINOR, VERSION)
API_VERSION = '2' # increments of 1; only change when something fundamental about how the API works changes. This way other nodes know how to communicate without learning too much information about you.
MIN_PY_VERSION = 7 # min version of 7 so we can take advantage of non-cyclic type hints
DEVELOPMENT_MODE = False
IS_QUBES = False
"""limit type length for a block (soft enforced, ignored if invalid but block still stored)."""
MAX_BLOCK_TYPE_LENGTH = 15
"""limit clock timestamp for new blocks to be skewed in the future in seconds,
2 minutes to allow plenty of time for slow block insertion and slight clock inaccuracies"""
MAX_BLOCK_CLOCK_SKEW = 120
"""Onionr user IDs are ed25519 keys, which are always 32 bytes in length"""
MAIN_PUBLIC_KEY_SIZE = 32
ORIG_RUN_DIR_ENV_VAR = 'ORIG_ONIONR_RUN_DIR'
DATABASE_LOCK_TIMEOUT = 60
# Block creation anonymization requirements
MIN_BLOCK_UPLOAD_PEER_PERCENT = 0.1
WSGI_SERVER_REQUEST_TIMEOUT_SECS = 120
MAX_NEW_PEER_QUEUE = 1000
BLOCK_EXPORT_FILE_EXT = '.onionr'
# Begin OnionrValues migrated values
"""30 days is plenty of time for someone to decide to renew a block"""
DEFAULT_EXPIRE = 2678400
# Metadata header section length limits, in bytes
BLOCK_METADATA_LENGTHS = {'meta': 1000, 'sig': 200, 'signer': 200, 'time': 10,
'n': 1000, 'c': 1000, 'encryptType': 4, 'expire': 14}
# Pool Eligibility Max Age
BLOCK_POOL_MAX_AGE = 300
"""Public key that signs MOTD messages shown in the web UI"""
MOTD_SIGN_KEY = "TRH763JURNY47QPBTTQ4LLPYCYQK6Q5YA33R6GANKZK5C5DKCIGQ"
"""Public key that signs update notifications."""
UPDATE_SIGN_KEY = "TRH763JURNY47QPBTTQ4LLPYCYQK6Q5YA33R6GANKZK5C5DKCIGQ"
if os.path.exists(filepaths.daemon_mark_file):
SCRIPT_NAME = 'start-daemon.sh'
else:
SCRIPT_NAME = 'onionr.sh'
if 'qubes' in platform.release().lower():
IS_QUBES = True

View File

@ -1,77 +0,0 @@
"""Onionr - Private P2P Communication.
Test Onionr as it is running
"""
import os
from secrets import SystemRandom
import logger
from onionrutils import epoch
from . import uicheck
from .webpasstest import webpass_test
from .osver import test_os_ver_endpoint
from .dnsrebindingtest import test_dns_rebinding
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
RUN_TESTS = [uicheck.check_ui,
webpass_test,
test_os_ver_endpoint,
]
SUCCESS_FILE = os.path.dirname(os.path.realpath(__file__)) + '/../../tests/runtime-result.txt'
class OnionrRunTestManager:
def __init__(self):
self.success: bool = True
self.run_date: int = 0
def run_tests(self):
tests = list(RUN_TESTS)
SystemRandom().shuffle(tests)
cur_time = epoch.get_epoch()
logger.info(f"Doing runtime tests at {cur_time}")
try:
os.remove(SUCCESS_FILE)
except FileNotFoundError:
pass
done_count: int = 0
total_to_do: int = len(tests)
try:
for i in tests:
last = i
logger.info("[RUNTIME TEST] " + last.__name__ + " started",
terminal=True, timestamp=True)
i(self)
done_count += 1
logger.info("[RUNTIME TEST] " + last.__name__ +
f" passed {done_count}/{total_to_do}",
terminal=True, timestamp=True)
except (ValueError, AttributeError):
logger.error(last.__name__ + ' failed assertions', terminal=True)
except Exception as e:
logger.error(last.__name__ + ' failed with non-asserting exception')
logger.error(str(e))
else:
ep = str(epoch.get_epoch())
logger.info(f'All runtime tests passed at {ep}', terminal=True)
with open(SUCCESS_FILE, 'w') as f:
f.write(ep)

View File

@ -1,46 +0,0 @@
"""Onionr - Private P2P Communication.
Test apis for dns rebinding
"""
import config
import requests
from filepaths import private_API_host_file, public_API_host_file
import logger
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def test_dns_rebinding(test_manager):
f = ''
with open(private_API_host_file, 'r') as f:
host = f.read()
private_api_port = config.get('client.client.port')
if requests.get(f'http://{host}:{private_api_port}/ping', headers={'host': 'example.com'}) == 'pong!':
raise ValueError('DNS rebinding failed')
logger.info('It is normal to see 403 errors right now', terminal=True)
if config.get('general.security_level', 0) > 0 or not config.get('transports.tor', True):
return
public_api_port = config.get('client.public.port')
f = ''
with open(public_API_host_file, 'r') as f:
host = f.read()
if requests.get(f'http://{host}:{public_api_port}/ping', headers={'host': 'example.com'}) == 'pong!':
raise ValueError('DNS rebinding failed')
logger.info('It is normal to see 403 errors right now', terminal=True)

View File

@ -1,8 +0,0 @@
import platform
from onionrutils import localcommand
def test_os_ver_endpoint(test_manager):
if localcommand.local_command('os') != platform.system().lower():
raise ValueError('could not get proper os platform from endpoint /os')

View File

@ -1,9 +0,0 @@
from onionrutils import localcommand
def check_ui(test_manager):
endpoints = ['/']
for point in endpoints:
result = localcommand.local_command(point)
if not result: raise ValueError
result = result.lower()
if 'script' not in result:
raise ValueError(f'uicheck failed on {point}')

View File

@ -1,11 +0,0 @@
import requests
from onionrutils import localcommand
def webpass_test(test_manager):
if requests.get('http://' + localcommand.get_hostname() + '/ping') == \
'pong!':
raise ValueError
if localcommand.local_command('ping') != 'pong!':
raise ValueError('Could not ping with normal localcommand in webpasstest')

View File

@ -1,34 +0,0 @@
"""Onionr - Private P2P Communication.
Initialize singleton deadsimplekv pseudo globals
"""
import queue
from typing import TYPE_CHECKING
from onionrutils import epoch
if TYPE_CHECKING:
from deadsimplekv import DeadSimpleKV
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def setup_kv(shared_vars: 'DeadSimpleKV'):
"""Init initial pseudo-globals."""
shared_vars.put('shutdown', False)
shared_vars.put('generating_blocks', [])
shared_vars.put('startTime', epoch.get_epoch())
shared_vars.put('isOnline', True)

View File

@ -1,30 +0,0 @@
"""Onionr - Private P2P Communication.
greenlet safe sleep, ignoring ctrl-c
"""
from gevent import sleep
from onionrutils.epoch import get_epoch
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
def better_sleep(wait: int):
"""Sleep catching ctrl c for wait seconds."""
start = get_epoch()
try:
sleep(wait)
except KeyboardInterrupt:
better_sleep(wait - (get_epoch() - start))

View File

@ -1,17 +0,0 @@
"""Onionr - Private P2P Communication.
read from a file from an offset (efficiently)
"""
from collections import namedtuple
OffsetReadResult = namedtuple('OffsetReadResult', ['data', 'new_offset'])
def read_from_offset(file_path, offset=0):
with open(file_path, 'rb') as f:
if offset:
f.seek(offset)
data = f.read()
offset = f.tell()
return OffsetReadResult(data, offset)

View File

@ -1,51 +0,0 @@
'''
Onionr - Private P2P Communication
z-fill (zero fill) a string to a specific length
intended for reconstructing block hashes
'''
from typing import Union
'''
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
def reconstruct_hash(hex_hash: Union[str, bytes],
length: int = 64) -> Union[str, bytes]:
"""Pad hash hex string with zeros, return result"""
return hex_hash.zfill(length)
def deconstruct_hash(hex_hash: Union[str, bytes]) -> Union[str, bytes]:
"""Remove leading zeros from hex hash, return result"""
new_hash = ''
ret_bytes = False
try:
hex_hash = hex_hash.decode()
ret_bytes = True
except AttributeError:
pass
c = 0
for x in hex_hash:
if x == '0':
c += 1
else:
break
new_hash = hex_hash[c:]
if ret_bytes:
new_hash = new_hash.encode()
return new_hash

View File

@ -13,4 +13,4 @@ echo "Future Onionr commands will use your set or default Onionr home directory,
echo "Ultimately, a live boot operating system such as Tails or Debian would be better for you to use."
$(dirname $0)/onionr.sh start & disown
sleep 2
$(dirname $0)/onionr.sh open-home
#$(dirname $0)/onionr.sh open-home

View File

@ -1 +0,0 @@
https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/robots.txt,http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/robots.txt,http://rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion/

View File

@ -5,10 +5,12 @@ Default example plugin for devs or to test blocks
import sys
import os
import locale
from time import sleep
import traceback
from typing import Set, TYPE_CHECKING
from threading import Thread, local
import blockdb
from gossip.peerset import gossip_peer_set
import logger
@ -17,7 +19,6 @@ import onionrblocks
locale.setlocale(locale.LC_ALL, '')
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
# import after path insert
from onionrutils.localcommand import local_command
"""
This program is free software: you can redistribute it and/or modify
@ -39,16 +40,20 @@ plugin_name = 'example'
PLUGIN_VERSION = '0.0.0'
def on_blocktest_cmd(api, data=None):
bl = onionrblocks.create_anonvdf_block(input("Enter a message:").encode('utf-8'), b"txt", 3600)
logger.info(
local_command(
'/addvdfblock',
post_data=bl.id + bl.raw,
silent=False, post=True),
terminal=True)
bl = onionrblocks.create_anonvdf_block(input("Enter a message:").encode('utf-8'), b"tst", 3600)
def on_printtest_cmd(api, data=None):
while True:
try:
print(list(blockdb.get_blocks_by_type("tst"))[0].data)
except IndexError:
pass
try:
sleep(1)
except KeyboardInterrupt:
break

View File

@ -1,28 +1,28 @@
import config
import logger
from gossip.peerset import gossip_peer_set
from getsocks import get_socks
from torpeer import TorPeer
MAX_TOR_PEERS = 20
def on_announce_rec(api, data=None):
socks_address, socks_port = get_socks()[0]
announced: str = data['address']
try:
announced = announced.decode('utf-8')
except AttributeError:
pass
announced = announced.strip()
if not announced.endswith('.onion'):
return
socks_address, socks_port = get_socks()[0]
if announced.removesuffix('.onion') == config.get(
'tor.transport_address', '').removesuffix('.onion'):
logger.warn(
"Received announcement for our own node, which shouldn't happen")
if announced.replace('.onion', '') == config.get(
'tor.transport_address', '').replace('.onion', ''):
return
if not announced.endswith('.onion'):
announced += '.onion'
logger.info(f"Peer {announced} announced to us.", terminal=True)

View File

@ -46,7 +46,7 @@ def on_bootstrap(api, data):
bootstrap_nodes = set(bootstrap_file_obj.read().split(','))
except FileNotFoundError:
bootstrap_nodes = set()
except Exception as e:
except Exception as _:
logger.warn(traceback.format_exc(), terminal=True)
return
else:
@ -57,12 +57,15 @@ def on_bootstrap(api, data):
while not config.get('tor.transport_address'):
sleep(1)
config.reload()
try:
config.reload()
except Exception:
logger.error(traceback.format_exc(), terminal=True)
socks_address, socks_port = get_socks()[0]
for address in bootstrap_nodes:
if address == config.get('tor.transport_address') or not address:
if address == config.get('tor.transport_address').replace('.onion', '') or not address:
continue
assert not address.endswith('.onion')
address += '.onion'

View File

@ -1 +1 @@
b3u6g7syd6ddicwoxe7ydelqnldyr6g5skvvmjzgh6duwjk6jhv2ixqd
jjvq7itovbt6gcttj5p25zgalht3zraqfvgzojhdqdd2rarriehrfnyd,m4vae3qvhnpz65jscbulv5j7lymhnbfv3hdhfwk7qztknldbgn3b3oqd

View File

@ -87,7 +87,9 @@ def on_gossip_start(api, data: Set[Peer] = None):
key_content='BEST', key_type='NEW', detached=True)
config.set('tor.key', add_onion_resp.private_key, savefile=True)
new_address = 'Generated '
config.set('tor.transport_address', add_onion_resp.service_id,
onion = add_onion_resp.service_id
onion = onion.removesuffix('.onion') + '.onion'
config.set('tor.transport_address', onion,
savefile=True)
else:
try:

View File

@ -1,5 +1,15 @@
import socks
from gossip.peerset import gossip_peer_set
import logger
class HandleRevc:
def __init__(self, sock):
self.sock_recv = sock.recv
def recv(self, *args, **kwargs):
return self.sock_recv(*args, **kwargs)
class TorPeer:
@ -18,7 +28,18 @@ class TorPeer:
s = socks.socksocket()
s.set_proxy(socks.SOCKS4, self.socks_host, self.socks_port, rdns=True)
s.settimeout(connect_timeout)
s.connect((self.onion_address, 80))
try:
s.connect((self.onion_address, 80))
except socks.GeneralProxyError:
try:
gossip_peer_set.remove(self)
except KeyError:
pass
else:
logger.debug(f"Could not create socket to peer {self.transport_address}", terminal=True)
raise
mock_recv = HandleRevc(s)
s.recv = mock_recv.recv
return s
def __hash__(self):

View File

@ -0,0 +1 @@
/dev/shm/onionr90775244/gossip-server.sock,/dev/shm/onionr1873728538/gossip-server.sock

View File

@ -0,0 +1,73 @@
"""Onionr - Private P2P Communication.
Unix transport plugin. Intended for testing Onionr networks using IPC
"""
import sys
import os
import locale
from time import sleep
import traceback
from typing import Set, TYPE_CHECKING
from threading import Thread
import shelve
import stem
from stem.control import Controller
import logger
from utils import readstatic
import config
from filepaths import gossip_server_socket_file
from gossip.peer import Peer
from gossip.peerset import gossip_peer_set
locale.setlocale(locale.LC_ALL, '')
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
# import after path insert
from unixpeer import UnixPeer
from unixbootstrap import on_bootstrap
from unixannounce import on_announce_rec
from unixfilepaths import peer_database_file
#from shutdown import on_shutdown_event
"""
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
plugin_name = 'unixtransport'
PLUGIN_VERSION = '0.0.0'
def on_shutdown_event(api, data=None):
with shelve.open(peer_database_file, 'c') as db:
for peer in gossip_peer_set:
if isinstance(peer, UnixPeer):
db[peer.transport_address] = peer
def on_init(api, data=None):
logger.info(
f"Unix Transport Plugin v{PLUGIN_VERSION} enabled", terminal=True)
logger.info(
f"Peers can connect to {gossip_server_socket_file}", terminal=True)
def on_get_our_transport(api, data=None):
callback_func = data['callback']
for_peer = data['peer']
if for_peer.transport_address == gossip_server_socket_file:
return
if data['peer'].__class__ == UnixPeer:
callback_func(for_peer, gossip_server_socket_file)

View File

@ -0,0 +1,24 @@
import config
import logger
from gossip.server import gossip_server_socket_file
from unixpeer import UnixPeer
def on_announce_rec(api, data=None):
announced: str = data['address']
try:
announced = announced.decode('utf-8')
except AttributeError:
pass
announced = announced.strip()
if not announced.endswith('.sock'):
return
if announced == gossip_server_socket_file:
return
logger.info(f"Peer {announced} announced to us.", terminal=True)
data['callback'](UnixPeer(announced))

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