import re import os import os.path import sys import time import struct import socket import pathlib import logging import argparse import tqdm import cbor2 from Crypto.Hash import SHA256 from Crypto.Cipher import Salsa20 from Crypto.Random import get_random_bytes BYAFN_ADMINSOCKET = os.getenv( 'BYAFN_ADMINSOCKET', default='./adminsocket' ) logging.basicConfig( format='%(asctime)s %(levelname)s: %(message)s', level=logging.INFO ) class Metafile: HEADER = b'\x80BYAFN-METAFILE\x00\x00' HEADER_LEN = len(HEADER) def __init__(self, filename, size, checksum, pieces, key=None): self.filename = filename self.size = size self.checksum = checksum self.pieces = pieces self.key = key def save(self, f): f.write(Metafile.HEADER) filename = self.filename.encode('UTF-8') filename_len = len(filename) f.write(struct.pack('!I', filename_len)) f.write(filename) f.write(struct.pack('!L', self.size)) f.write(self.checksum) if self.key: f.write(b'Y') f.write(self.key) else: f.write(b'N') pieces_count = len(self.pieces) f.write(struct.pack('!L', pieces_count)) for hash in self.pieces: f.write(hash) @staticmethod def load(f): header = f.read(Metafile.HEADER_LEN) if header != Metafile.HEADER: raise ValueError filename_len = f.read(4) filename_len = struct.unpack('!I', filename_len)[0] filename = f.read(filename_len) if len(filename) != filename_len: raise ValueError filename = filename.decode('UTF-8') filename = pathlib.Path(filename).name size = f.read(4) size = struct.unpack('!I', size)[0] checksum = f.read(32) if len(checksum) != 32: raise ValueError is_encrypted = f.read(1) if is_encrypted == b'Y': key = f.read(40) if len(key) != 40: raise ValueError elif is_encrypted == b'N': key = None else: raise ValueError pieces_count = f.read(4) pieces_count = struct.unpack('!L', pieces_count)[0] if pieces_count < 1: raise ValueError pieces = [] while pieces_count > 0: piece_hash = f.read(32) if len(piece_hash) != 32: raise ValueError pieces.append(piece_hash) pieces_count -= 1 return Metafile(filename, size, checksum, pieces, key) def sha256(data): hash = SHA256.new() hash.update(data) return hash.digest() def salsa20_create_encryptor(): key = get_random_bytes(32) cipher = Salsa20.new(key=key) return (cipher.nonce + key, cipher) def salsa20_create_decryptor(key): if len(key) != 40: raise ValueError cipher = Salsa20.new(key=key[8:], nonce=key[:8]) return cipher def parse_file_size(size): units = { 'b': 1, 'kb': 10e3, 'mb': 10e6 } match = re.match(r'(\d+)([km]b)', size.strip().lower()) if not match: raise ValueError size, unit = int(match.group(1)), units[match.group(2)] return int(size * unit) def send_command(**command): try: conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) conn.connect(args.admin_socket_path) command = cbor2.dumps(command) conn.send(struct.pack('