Documentation
This commit is contained in:
parent
3f5384f9e9
commit
d703054c58
40
README.md
40
README.md
@ -1,6 +1,6 @@
|
|||||||
# filebridging
|
# 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
|
## Requirements
|
||||||
Python3.8+ is needed for this package.
|
Python3.8+ is needed for this package.
|
||||||
@ -21,15 +21,33 @@ python -m filebridging.client --help
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Client-server example
|
* Client-server example
|
||||||
```bash
|
```bash
|
||||||
# 3 distinct tabs
|
# 3 distinct tabs
|
||||||
python -m filebridging.server --host localhost --port 5000 --certificate ~/.ssh/server.crt --key ~/.ssh/server.key
|
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 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
|
python -m filebridging.client r --host localhost --port 5000 --certificate ~/.ssh/server.crt --token 12345678 --password supersecretpasswordhere --path ~/Downloads
|
||||||
```
|
```
|
||||||
|
|
||||||
Client-client example
|
* Client-client example
|
||||||
```bash
|
```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 = '.'
|
||||||
|
```
|
||||||
|
@ -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 argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
@ -15,6 +28,11 @@ from . import utilities
|
|||||||
|
|
||||||
|
|
||||||
class Client:
|
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,
|
def __init__(self, host='localhost', port=5000, ssl_context=None,
|
||||||
action=None,
|
action=None,
|
||||||
standalone=False,
|
standalone=False,
|
||||||
@ -49,10 +67,15 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def host(self) -> str:
|
def host(self) -> str:
|
||||||
|
"""Host to reach.
|
||||||
|
|
||||||
|
For standalone clients, you must be able to listen this host.
|
||||||
|
"""
|
||||||
return self._host
|
return self._host
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self) -> int:
|
def port(self) -> int:
|
||||||
|
"""Port number."""
|
||||||
return self._port
|
return self._port
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -84,14 +107,25 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def buffer_length_limit(self) -> int:
|
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
|
return self._buffer_length_limit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def buffer_chunk_size(self) -> int:
|
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
|
return self._buffer_chunk_size
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_path(self) -> str:
|
def file_path(self) -> str:
|
||||||
|
"""Path of file to send or destination folder."""
|
||||||
return self._file_path
|
return self._file_path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -107,6 +141,11 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def token(self):
|
def token(self):
|
||||||
|
"""Session token.
|
||||||
|
|
||||||
|
6-10 alphanumerical characters to provide to server to link sender and
|
||||||
|
receiver.
|
||||||
|
"""
|
||||||
return self._token
|
return self._token
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -128,6 +167,7 @@ class Client:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def file_size_string(self):
|
def file_size_string(self):
|
||||||
|
"""Formatted file size (e.g. 64.22 MB)."""
|
||||||
return self._file_size_string
|
return self._file_size_string
|
||||||
|
|
||||||
async def run_client(self) -> None:
|
async def run_client(self) -> None:
|
||||||
@ -168,6 +208,13 @@ class Client:
|
|||||||
|
|
||||||
async def _connect(self, reader: asyncio.StreamReader,
|
async def _connect(self, reader: asyncio.StreamReader,
|
||||||
writer: asyncio.StreamWriter):
|
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:
|
try:
|
||||||
return await self.connect(reader, writer)
|
return await self.connect(reader, writer)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
@ -178,6 +225,12 @@ class Client:
|
|||||||
async def connect(self,
|
async def connect(self,
|
||||||
reader: asyncio.StreamReader,
|
reader: asyncio.StreamReader,
|
||||||
writer: asyncio.StreamWriter):
|
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._reader = reader
|
||||||
self._writer = writer
|
self._writer = writer
|
||||||
|
|
||||||
@ -250,6 +303,10 @@ class Client:
|
|||||||
await self.receive(reader=self.reader)
|
await self.receive(reader=self.reader)
|
||||||
|
|
||||||
async def encrypt_file(self, input_file, output_file):
|
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
|
self._encryption_complete = False
|
||||||
logging.info("Encrypting file...")
|
logging.info("Encrypting file...")
|
||||||
stdout, stderr = ''.encode(), ''.encode()
|
stdout, stderr = ''.encode(), ''.encode()
|
||||||
@ -273,6 +330,11 @@ class Client:
|
|||||||
self._encryption_complete = True
|
self._encryption_complete = True
|
||||||
|
|
||||||
async def send(self, writer: asyncio.StreamWriter):
|
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
|
self._working = True
|
||||||
file_path = self.file_path
|
file_path = self.file_path
|
||||||
if self.password:
|
if self.password:
|
||||||
@ -327,6 +389,10 @@ class Client:
|
|||||||
return
|
return
|
||||||
|
|
||||||
async def receive(self, reader: asyncio.StreamReader):
|
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
|
self._working = True
|
||||||
file_path = os.path.join(
|
file_path = os.path.join(
|
||||||
os.path.abspath(
|
os.path.abspath(
|
||||||
@ -355,6 +421,11 @@ class Client:
|
|||||||
break
|
break
|
||||||
file_to_receive.write(input_data)
|
file_to_receive.write(input_data)
|
||||||
print() # New line after sys.stdout.write
|
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.")
|
logging.info("File received.")
|
||||||
if self.password:
|
if self.password:
|
||||||
logging.info("Decrypting file...")
|
logging.info("Decrypting file...")
|
||||||
@ -688,17 +759,16 @@ def main():
|
|||||||
else:
|
else:
|
||||||
logging.info("Proceeding without storing values...")
|
logging.info("Proceeding without storing values...")
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
if certificate is not None:
|
if certificate and key and standalone: # Standalone client
|
||||||
if key is None: # Server-dependent client
|
ssl_context = ssl.create_default_context(
|
||||||
ssl_context = ssl.create_default_context(
|
purpose=ssl.Purpose.CLIENT_AUTH
|
||||||
purpose=ssl.Purpose.SERVER_AUTH
|
)
|
||||||
)
|
ssl_context.load_cert_chain(certificate, key)
|
||||||
ssl_context.load_verify_locations(certificate)
|
elif certificate: # Server-dependent client
|
||||||
else: # Standalone client
|
ssl_context = ssl.create_default_context(
|
||||||
ssl_context = ssl.create_default_context(
|
purpose=ssl.Purpose.SERVER_AUTH
|
||||||
purpose=ssl.Purpose.CLIENT_AUTH
|
)
|
||||||
)
|
ssl_context.load_verify_locations(certificate)
|
||||||
ssl_context.load_cert_chain(certificate, key)
|
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"Please consider using SSL. To do so, add in `config.py` or "
|
"Please consider using SSL. To do so, add in `config.py` or "
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
"""Server class.
|
"""Server class.
|
||||||
|
|
||||||
May be a local server or a publicly reachable server.
|
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
|
import argparse
|
||||||
|
2
setup.py
2
setup.py
@ -45,7 +45,7 @@ setuptools.setup(
|
|||||||
author=find_information("author", "filebridging", "__init__.py"),
|
author=find_information("author", "filebridging", "__init__.py"),
|
||||||
author_email=find_information("email", "filebridging", "__init__.py"),
|
author_email=find_information("email", "filebridging", "__init__.py"),
|
||||||
description=(
|
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."
|
"encryption."
|
||||||
),
|
),
|
||||||
license=find_information("license", "filebridging", "__init__.py"),
|
license=find_information("license", "filebridging", "__init__.py"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user