Compare commits
16 Commits
b9fa446cb0
...
c880b6fa7a
Author | SHA1 | Date |
---|---|---|
Kevin F | c880b6fa7a | |
Kevin F | 228d3cbe35 | |
Kevin F | 9864fa5040 | |
Kevin F | 3a7e378d8b | |
Kevin F | f220e398f1 | |
Kevin F | 8bd4a4c524 | |
Kevin F | c663be30f1 | |
Kevin F | 90176e43fb | |
Kevin F | 9b6d1f5dbd | |
Kevin F | e7daaf576f | |
Kevin F | 84c13ade51 | |
Kevin F | d2b5298bc6 | |
Kevin F | d11d12b67f | |
Kevin F | 1eadb4bf6e | |
Kevin F | 64a88118bd | |
Kevin F | b25e376349 |
|
@ -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
|
500
requirements.txt
500
requirements.txt
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
|
@ -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)
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__]
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .streamfrom import stream_from_peers
|
||||
from .streamto import stream_to_peer
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
last_incoming_timestamp = 0
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -1 +0,0 @@
|
|||
from . import shutdown, setbindip
|
|
@ -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
|
|
@ -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")
|
|
@ -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')
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
from . import staticfiles, endpoints
|
|
@ -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())
|
||||
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||
from . import client
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,6 +0,0 @@
|
|||
# sse
|
||||
|
||||
This folder contains a wrapper for handling server sent event loops
|
||||
|
||||
|
||||
|
|
@ -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/>.
|
||||
"""
|
|
@ -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)
|
||||
|
|
@ -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
|
|
@ -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')
|
|
@ -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)
|
|
@ -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())
|
||||
|
|
|
@ -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
|
|
@ -8,8 +8,7 @@ QUOTES = [
|
|||
""),
|
||||
("Study after study has show that human behavior changes when we know we’re 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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
from onionrutils import epoch
|
||||
def replay_timestamp_validation(timestamp):
|
||||
return epoch.get_epoch() - int(timestamp) <= 2419200
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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...')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
'''
|
||||
|
||||
import onionrplugins, logger
|
||||
from onionrutils import localcommand
|
||||
|
||||
|
||||
class PluginAPI:
|
||||
def __init__(self, pluginapi):
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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)
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
from urllib3.contrib.socks import SOCKSProxyManager # noqa
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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')
|
|
@ -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}')
|
|
@ -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')
|
|
@ -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)
|
|
@ -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))
|
||||
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/robots.txt,http://2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid.onion/robots.txt,http://rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion/
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -1 +1 @@
|
|||
b3u6g7syd6ddicwoxe7ydelqnldyr6g5skvvmjzgh6duwjk6jhv2ixqd
|
||||
jjvq7itovbt6gcttj5p25zgalht3zraqfvgzojhdqdd2rarriehrfnyd,m4vae3qvhnpz65jscbulv5j7lymhnbfv3hdhfwk7qztknldbgn3b3oqd
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/dev/shm/onionr90775244/gossip-server.sock,/dev/shm/onionr1873728538/gossip-server.sock
|
|
@ -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)
|
|
@ -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
Loading…
Reference in New Issue