Queer European MD passionate about IT
Browse Source

Documentation

Davte 5 years ago
parent
commit
d703054c58
4 changed files with 118 additions and 24 deletions
  1. 29 11
      README.md
  2. 82 12
      filebridging/client.py
  3. 6 0
      filebridging/server.py
  4. 1 1
      setup.py

+ 29 - 11
README.md

@@ -1,6 +1,6 @@
 # filebridging
 
-Share files via a bridge server using TCP over SSL and aes-256-cbc encryption.
+Share files via a bridge server using TCP over SSL and end-to-end encryption.
 
 ## Requirements
 Python3.8+ is needed for this package.
@@ -21,15 +21,33 @@ python -m filebridging.client --help
 ```
 
 ## Examples
-Client-server example
-```bash
-# 3 distinct tabs
-python -m filebridging.server --host localhost --port 5000 --certificate ~/.ssh/server.crt --key ~/.ssh/server.key
-python -m filebridging.client s --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/file_to_send 
-python -m filebridging.client r --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/Downloads 
-```
+* Client-server example
+    ```bash
+    # 3 distinct tabs
+    python -m filebridging.server --host localhost --port 5000 --certificate ~/.ssh/server.crt --key ~/.ssh/server.key
+    python -m filebridging.client s --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/file_to_send 
+    python -m filebridging.client r --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/Downloads 
+    ```
 
-Client-client example
-```bash
+* Client-client example
+    ```bash
+    # 2 distinct tabs
+    python -m filebridging.client s --host localhost --port 5000 --certificate ~/.ssh/server.crt --key ~/.ssh/private.key --token 12345678 --password supersecretpasswordhere --path ~/file_to_send --standalone
+    python -m filebridging.client r --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/Downloads 
+    ```
+    The receiver client may be standalone as well: just add the `--key` parameter (for SSL-secured sessions) and the `--standalone` flag. 
 
-```
+* Configuration file example
+    ```python
+    #!/bin/python
+    
+    host = "www.example.com"
+    port = 5000
+    certificate = "/path/to/public.crt"
+    key = "/path/to/private.key"
+    
+    action = 'r'
+    password = 'verysecretpassword'
+    token = 'sessiontok'
+    file_path = '.'
+    ```

+ 82 - 12
filebridging/client.py

@@ -1,4 +1,17 @@
-"""Receiver and sender client class."""
+"""Receiver and sender client class.
+
+Arguments
+    - host: localhost, IPv4 address or domain (e.g. www.example.com)
+    - port: port to reach (must be enabled)
+    - action: either [S]end or [R]eceive
+    - file_path: file to send / destination folder
+    - token: session token (6-10 alphanumerical characters)
+    - certificate [optional]: server certificate for SSL
+    - key [optional]: needed only for standalone clients
+    - password [optional]: necessary to end-to-end encryption
+    - standalone [optional]: allow client-to-client communication (the host
+    must be reachable by both clients)
+"""
 
 import argparse
 import asyncio
@@ -15,6 +28,11 @@ from . import utilities
 
 
 class Client:
+    """Sender or receiver client.
+
+    Create a Client object providing host, port and other optional parameters.
+    Then, run it with `Client().run()` method
+    """
     def __init__(self, host='localhost', port=5000, ssl_context=None,
                  action=None,
                  standalone=False,
@@ -49,10 +67,15 @@ class Client:
 
     @property
     def host(self) -> str:
+        """Host to reach.
+
+        For standalone clients, you must be able to listen this host.
+        """
         return self._host
 
     @property
     def port(self) -> int:
+        """Port number."""
         return self._port
 
     @property
@@ -84,14 +107,25 @@ class Client:
 
     @property
     def buffer_length_limit(self) -> int:
+        """Max number of buffer chunks in memory.
+
+        You may want to reduce this limit to allocate less memory, or increase
+        it to boost performance.
+        """
         return self._buffer_length_limit
 
     @property
     def buffer_chunk_size(self) -> int:
+        """Length (bytes) of buffer chunks in memory.
+
+        You may want to reduce this limit to allocate less memory, or increase
+        it to boost performance.
+        """
         return self._buffer_chunk_size
 
     @property
     def file_path(self) -> str:
+        """Path of file to send or destination folder."""
         return self._file_path
 
     @property
@@ -107,6 +141,11 @@ class Client:
 
     @property
     def token(self):
+        """Session token.
+
+        6-10 alphanumerical characters to provide to server to link sender and
+        receiver.
+        """
         return self._token
 
     @property
@@ -128,6 +167,7 @@ class Client:
 
     @property
     def file_size_string(self):
+        """Formatted file size (e.g. 64.22 MB)."""
         return self._file_size_string
 
     async def run_client(self) -> None:
@@ -168,6 +208,13 @@ class Client:
 
     async def _connect(self, reader: asyncio.StreamReader,
                        writer: asyncio.StreamWriter):
+        """Wrap connect method to catch exceptions.
+
+        This is required since callbacks are never awaited and potential
+        exception would be logged at loop.close().
+        Only standalone clients need this wrapper, regular clients might use
+        connect method directly.
+        """
         try:
             return await self.connect(reader, writer)
         except KeyboardInterrupt:
@@ -178,6 +225,12 @@ class Client:
     async def connect(self,
                       reader: asyncio.StreamReader,
                       writer: asyncio.StreamWriter):
+        """Communicate with the server or the other client.
+
+        Send information about the client (connection token, role, file name
+        and size), get information from the server (file name and size), wait
+        for start signal and then send or receive the file.
+        """
         self._reader = reader
         self._writer = writer
 
@@ -250,6 +303,10 @@ class Client:
             await self.receive(reader=self.reader)
 
     async def encrypt_file(self, input_file, output_file):
+        """Use openssl to encrypt the input_file.
+
+        The encrypted file will overwrite `output_file` if it exists.
+        """
         self._encryption_complete = False
         logging.info("Encrypting file...")
         stdout, stderr = ''.encode(), ''.encode()
@@ -273,6 +330,11 @@ class Client:
         self._encryption_complete = True
 
     async def send(self, writer: asyncio.StreamWriter):
+        """Encrypt and send the file.
+
+        Caution: if no password is provided, the file will be sent as clear
+        text.
+        """
         self._working = True
         file_path = self.file_path
         if self.password:
@@ -327,6 +389,10 @@ class Client:
         return
 
     async def receive(self, reader: asyncio.StreamReader):
+        """Download the file and decrypt it.
+
+        If no password is provided, the file cannot be decrypted.
+        """
         self._working = True
         file_path = os.path.join(
             os.path.abspath(
@@ -355,6 +421,11 @@ class Client:
                     break
                 file_to_receive.write(input_data)
         print()  # New line after sys.stdout.write
+        if bytes_received < self.file_size:
+            logging.warning("Transmission terminated too soon!")
+            if self.password:
+                logging.error("Partial files can not be decrypted!")
+                return
         logging.info("File received.")
         if self.password:
             logging.info("Decrypting file...")
@@ -688,17 +759,16 @@ def main():
         else:
             logging.info("Proceeding without storing values...")
     ssl_context = None
-    if certificate is not None:
-        if key is None:  # Server-dependent client
-            ssl_context = ssl.create_default_context(
-                purpose=ssl.Purpose.SERVER_AUTH
-            )
-            ssl_context.load_verify_locations(certificate)
-        else:  # Standalone client
-            ssl_context = ssl.create_default_context(
-                purpose=ssl.Purpose.CLIENT_AUTH
-            )
-            ssl_context.load_cert_chain(certificate, key)
+    if certificate and key and standalone:  # Standalone client
+        ssl_context = ssl.create_default_context(
+            purpose=ssl.Purpose.CLIENT_AUTH
+        )
+        ssl_context.load_cert_chain(certificate, key)
+    elif certificate:  # Server-dependent client
+        ssl_context = ssl.create_default_context(
+            purpose=ssl.Purpose.SERVER_AUTH
+        )
+        ssl_context.load_verify_locations(certificate)
     else:
         logging.warning(
             "Please consider using SSL. To do so, add in `config.py` or "

+ 6 - 0
filebridging/server.py

@@ -1,6 +1,12 @@
 """Server class.
 
 May be a local server or a publicly reachable server.
+
+Arguments
+    - host: localhost, IPv4 address or domain (e.g. www.example.com)
+    - port: port to reach (must be enabled)
+    - certificate [optional]: server certificate for SSL
+    - key [optional]: needed only for standalone clients
 """
 
 import argparse

+ 1 - 1
setup.py

@@ -45,7 +45,7 @@ setuptools.setup(
     author=find_information("author", "filebridging", "__init__.py"),
     author_email=find_information("email", "filebridging", "__init__.py"),
     description=(
-        "Share files via a bridge server using TCP over SSL and aes-256-cbc "
+        "Share files via a bridge server using TCP over SSL and end-to-end "
         "encryption."
     ),
     license=find_information("license", "filebridging", "__init__.py"),