diff --git a/pyseeder.py b/pyseeder.py index 02aa3d5..b008663 100755 --- a/pyseeder.py +++ b/pyseeder.py @@ -1,48 +1,12 @@ #! /usr/bin/env python3 -import os, os.path +import os import sys -from getpass import getpass import argparse -from pyseeder.crypto import keygen -from pyseeder.su3file import SU3File import pyseeder.transport - +import pyseeder.actions from pyseeder.utils import PyseederException - -def keygen_action(args): - """Sub-command to generate keys""" - priv_key_password = getpass("Set private key password: ").encode("utf-8") - keygen(args.cert, args.private_key, priv_key_password, args.signer_id) - -def reseed_action(args): - """Sub-command to generate reseed file""" - priv_key_password = input().encode("utf-8") - su3file = SU3File(args.signer_id) - su3file.reseed(args.netdb) - su3file.write(args.outfile, args.private_key, priv_key_password) - -def transport_pull_action(args): - """Sub-command for downloading su3 file""" - import random - random.shuffle(args.urls) - - for u in args.urls: - if pyseeder.transport.download(u, args.outfile): - return True - - raise PyseederException("Failed to download su3 file") - -def transport_push_action(args): - """Sub-command for uploading su3 file with transports""" - if not os.path.isfile(args.config): - raise PyseederException("Can't read transports config file") - - import configparser - config = configparser.ConfigParser() - config.read(args.config) - pyseeder.transport.upload(args.file, config) - + def main(): parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title="actions", @@ -60,8 +24,8 @@ def main(): kg_parser.add_argument("--private-key", default="data/priv_key.pem", help="RSA private key (default: data/priv_key.pem)") kg_parser.add_argument("--cert", required=True, - help="Certificate (example: output/user_at_mail.i2p.crt)") - kg_parser.set_defaults(func=keygen_action) + help="Certificate (example: data/user_at_mail.i2p.crt)") + kg_parser.set_defaults(func=pyseeder.actions.keygen) rs_parser = subparsers.add_parser( @@ -80,7 +44,7 @@ echo $YOUR_PASSWORD | %(prog)s --netdb /path/to/netDb \\ help="Output file (default: output/i2pseeds.su3)") rs_parser.add_argument("--netdb", required=True, help="Path to netDb folder (example: ~/.i2pd/netDb)") - rs_parser.set_defaults(func=reseed_action) + rs_parser.set_defaults(func=pyseeder.actions.reseed) tpull_parser = subparsers.add_parser( @@ -96,7 +60,7 @@ echo $YOUR_PASSWORD | %(prog)s --netdb /path/to/netDb \\ mainline I2P (like https://reseed.i2p-projekt.de/)""") tpull_parser.add_argument("-o", "--outfile", default="output/i2pseeds.su3", help="Output file (default: output/i2pseeds.su3)") - tpull_parser.set_defaults(func=transport_pull_action) + tpull_parser.set_defaults(func=pyseeder.actions.transport_pull) tpush_parser = subparsers.add_parser( @@ -108,7 +72,30 @@ echo $YOUR_PASSWORD | %(prog)s --netdb /path/to/netDb \\ help="Transports config file (default: transports.ini)") tpush_parser.add_argument("-f", "--file", default="output/i2pseeds.su3", help=".su3 file (default: output/i2pseeds.su3)") - tpush_parser.set_defaults(func=transport_push_action) + tpush_parser.set_defaults(func=pyseeder.actions.transport_push) + + + serve_parser = subparsers.add_parser( + "serve", + description="""Run HTTPS reseeding server + (in production use nginx instead, please). + Will ask for a private key password""", + usage="""%(prog)s --port 8443 --host 127.0.0.1 \\ + --private-key data/priv_key.pem \\ + --cert data/user_at_mail.i2p.crt \\ + --file output/i2pseeds.su3""" + ) + serve_parser.add_argument("--host", default="0.0.0.0", + help="Host listening for clients (default: 0.0.0.0)") + serve_parser.add_argument("--port", default=8443, + help="Port listening for clients (default: 8443)") + serve_parser.add_argument("--private-key", default="data/priv_key.pem", + help="RSA private key (default: data/priv_key.pem)") + serve_parser.add_argument("--cert", required=True, + help="Certificate (example: data/user_at_mail.i2p.crt)") + serve_parser.add_argument("-f", "--file", default="output/i2pseeds.su3", + help=".su3 file (default: output/i2pseeds.su3)") + serve_parser.set_defaults(func=pyseeder.actions.serve) args = parser.parse_args() if hasattr(args, "func"): diff --git a/pyseeder/actions.py b/pyseeder/actions.py new file mode 100644 index 0000000..ba03c13 --- /dev/null +++ b/pyseeder/actions.py @@ -0,0 +1,52 @@ +"""Action functions for argparser""" +import pyseeder.transport +import pyseeder.actions +from pyseeder.utils import PyseederException, check_readable, check_writable + +def keygen(args): + """Sub-command to generate keys""" + for f in [args.cert, args.private_key]: check_writable(f) + + from pyseeder.crypto import keygen + from getpass import getpass + priv_key_password = getpass("Set private key password: ").encode("utf-8") + keygen(args.cert, args.private_key, priv_key_password, args.signer_id) + +def reseed(args): + """Sub-command to generate reseed file""" + check_writable(args.outfile) + for f in [args.netdb, args.private_key]: check_readable(f) + + from pyseeder.su3file import SU3File + priv_key_password = input().encode("utf-8") + su3file = SU3File(args.signer_id) + su3file.reseed(args.netdb) + su3file.write(args.outfile, args.private_key, priv_key_password) + +def transport_pull(args): + """Sub-command for downloading su3 file""" + import random + random.shuffle(args.urls) + + for u in args.urls: + if pyseeder.transport.download(u, args.outfile): + return True + + raise PyseederException("Failed to download su3 file") + +def transport_push(args): + """Sub-command for uploading su3 file with transports""" + check_readable(args.config) + + import configparser + config = configparser.ConfigParser() + config.read(args.config) + pyseeder.transport.upload(args.file, config) + +def serve(args): + """Sub-command to start HTTPS reseed server""" + for f in [args.private_key, args.cert, args.file]: check_readable(f) + + import pyseeder.server + pyseeder.server.run_server(args.host, args.port, args.private_key, + args.cert, args.file) diff --git a/pyseeder/crypto.py b/pyseeder/crypto.py index b0bb9f8..3a9e8eb 100644 --- a/pyseeder/crypto.py +++ b/pyseeder/crypto.py @@ -1,4 +1,4 @@ -import os, os.path +import os import random import sys import datetime @@ -15,10 +15,6 @@ from cryptography.hazmat.primitives.asymmetric import padding def keygen(pub_key, priv_key, priv_key_password, user_id): """Generate new private key and certificate RSA_SHA512_4096""" - for f in [pub_key, priv_key]: - if not os.access(os.path.dirname(f) or ".", os.W_OK): - raise PyseederException("Can't write {}, access forbidden").format(f) - # Generate our key key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend()) @@ -63,12 +59,6 @@ def keygen(pub_key, priv_key, priv_key_password, user_id): def append_signature(target_file, priv_key, priv_key_password): """Append signature to the end of file""" - if not os.path.exists(priv_key): - raise PyseederException("Wrong private key path") - - if not os.access(priv_key, os.R_OK): - raise PyseederException("Can't read private key, access forbidden") - with open(target_file, "rb") as f: contents = f.read() diff --git a/pyseeder/server.py b/pyseeder/server.py new file mode 100644 index 0000000..4b575c9 --- /dev/null +++ b/pyseeder/server.py @@ -0,0 +1,34 @@ +import http.server +import urllib.parse +import ssl + +class ReseedHandler(http.server.SimpleHTTPRequestHandler): + """Handles reseeding requests""" + i2pseeds_file = "" + server_version = "Pyseeder Server" + sys_version = "" + + def do_GET(self): + path = urllib.parse.urlparse(self.path).path + if path == "/i2pseeds.su3": + self.send_response(200) + self.send_header("Content-Type", "application/octet-stream") + self.end_headers() + with open(self.i2pseeds_file, 'rb') as f: + self.wfile.write(f.read()) + else: + self.send_error(404, "Not found") + +def run_server(host, port, priv_key, cert, i2pseeds_file): + """Start HTTPS server""" + Handler = ReseedHandler + Handler.i2pseeds_file = i2pseeds_file + + httpd = http.server.HTTPServer((host, int(port)), Handler) + httpd.socket = ssl.wrap_socket(httpd.socket, server_side=True, + keyfile=priv_key, certfile=cert, ssl_version=ssl.PROTOCOL_TLSv1) + + try: + httpd.serve_forever() + except KeyboardInterrupt: + exit() diff --git a/pyseeder/su3file.py b/pyseeder/su3file.py index fa80238..af57847 100644 --- a/pyseeder/su3file.py +++ b/pyseeder/su3file.py @@ -1,4 +1,4 @@ -import os, os.path +import os import time, datetime import random import io @@ -27,9 +27,6 @@ class SU3File: def write(self, filename, priv_key, priv_key_password): """Write file to disc""" - if not os.access(os.path.dirname(filename) or ".", os.W_OK): - raise PyseederException("Can't write su3 file, access forbidden") - nullbyte = bytes([0]) with open(filename, "wb") as f: f.write("I2Psu3".encode("utf-8")) @@ -58,12 +55,6 @@ class SU3File: zip_file = io.BytesIO() dat_files = [] - if not os.path.exists(netdb): - raise PyseederException("Wrong netDb path") - - if not os.access(netdb, os.R_OK): - raise PyseederException("Can't read netDb, access forbidden") - for root, dirs, files in os.walk(netdb): for f in files: if f.endswith(".dat"): @@ -71,11 +62,13 @@ class SU3File: # may be not older than 10h dat_files.append(os.path.join(root, f)) - if len(dat_files) < 100: - raise PyseederException("Can't get enough netDb entries. Wrong netDb path?") + if len(dat_files) == 0: + raise PyseederException("Can't get enough netDb entries") + elif len(dat_files) > 75: + dat_files = random.sample(dat_files, 75) with ZipFile(zip_file, "w", compression=ZIP_DEFLATED) as zf: - for f in random.sample(dat_files, 75): + for f in dat_files: zf.write(f, arcname=os.path.split(f)[1]) self.FILE_TYPE = 0x00 diff --git a/pyseeder/transport.py b/pyseeder/transport.py index 76d1700..3735734 100644 --- a/pyseeder/transport.py +++ b/pyseeder/transport.py @@ -1,7 +1,7 @@ """Module for managing transport tasks""" import urllib.request from urllib.error import URLError -import os, os.path +import os import importlib from pyseeder.utils import PyseederException diff --git a/pyseeder/transports/git.py b/pyseeder/transports/git.py index 0d9db6a..5b839ef 100644 --- a/pyseeder/transports/git.py +++ b/pyseeder/transports/git.py @@ -1,6 +1,6 @@ """Git transport plugin""" import subprocess -import os, os.path +import os from shutil import copyfile from pyseeder.utils import TransportException diff --git a/pyseeder/utils.py b/pyseeder/utils.py index 3a56eaa..f6ebbef 100644 --- a/pyseeder/utils.py +++ b/pyseeder/utils.py @@ -1,7 +1,18 @@ """Various code""" +import os class PyseederException(Exception): pass class TransportException(PyseederException): pass + +def check_readable(f): + """Checks if path exists and readable""" + if not os.path.exists(f) or not os.access(f, os.R_OK): + raise PyseederException("Error accessing path: {}".format(f)) + +def check_writable(f): + """Checks if path is writable""" + if not os.access(os.path.dirname(f) or ".", os.W_OK): + raise PyseederException("Path is not writable: {}".format(f))