From f4006045c9d95733b03b605ebefde4ddb4bcabb7 Mon Sep 17 00:00:00 2001 From: Peter Badida Date: Sun, 3 Oct 2021 14:08:08 +0200 Subject: [PATCH 1/2] add guard against writing to existing, non-seekable media --- pykeepass/pykeepass.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pykeepass/pykeepass.py b/pykeepass/pykeepass.py index 5a66d761..68442bf1 100644 --- a/pykeepass/pykeepass.py +++ b/pykeepass/pykeepass.py @@ -11,6 +11,7 @@ import uuid import zlib from copy import deepcopy +from os.path import exists from construct import Container, ChecksumError from lxml import etree @@ -128,7 +129,7 @@ def reload(self): self.read(self.filename, self.password, self.keyfile) - def save(self, filename=None, transformed_key=None): + def save(self, filename=None, transformed_key=None, force=False): """Save current database object to disk. Args: @@ -137,12 +138,21 @@ def save(self, filename=None, transformed_key=None): PyKeePass.filename is unchanged. transformed_key (:obj:`bytes`, optional): precomputed transformed key. + force (:obj:`bool`, optional): force write if preconditions fail """ + seek_msg = ( + 'Using a non-seekable medium may cause KDBX corruption,' + ' use force=True to override' + ) + output = None if not filename: filename = self.filename if hasattr(filename, "write"): + if not getattr(filename, "seekable", lambda: 0)() and not force: + raise Exception(seek_msg) + output = KDBX.build_stream( self.kdbx, filename, @@ -150,7 +160,13 @@ def save(self, filename=None, transformed_key=None): keyfile=self.keyfile, transformed_key=transformed_key ) + else: + if exists(filename): + with open(filename) as file: + if not file.seekable() and not force: + raise Exception(seek_msg) + output = KDBX.build_file( self.kdbx, filename, From f3eed51622941775c6b95eb6246d1c3d7a0a3696 Mon Sep 17 00:00:00 2001 From: Peter Badida Date: Sun, 3 Oct 2021 18:17:44 +0200 Subject: [PATCH 2/2] add option to save KDBX to socket --- pykeepass/pykeepass.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pykeepass/pykeepass.py b/pykeepass/pykeepass.py index 68442bf1..a5640524 100644 --- a/pykeepass/pykeepass.py +++ b/pykeepass/pykeepass.py @@ -10,8 +10,11 @@ import re import uuid import zlib +import socket from copy import deepcopy from os.path import exists +from stat import S_ISSOCK +from io import BytesIO from construct import Container, ChecksumError from lxml import etree @@ -130,10 +133,11 @@ def reload(self): self.read(self.filename, self.password, self.keyfile) def save(self, filename=None, transformed_key=None, force=False): - """Save current database object to disk. + """Save current database object to disk, buffer or socket. Args: - filename (:obj:`str`, optional): path to database or stream object. + filename (:obj:`str`, optional): path to database, stream object + or socket. If None, the path given when the database was opened is used. PyKeePass.filename is unchanged. transformed_key (:obj:`bytes`, optional): precomputed transformed @@ -161,6 +165,20 @@ def save(self, filename=None, transformed_key=None, force=False): transformed_key=transformed_key ) + elif exists(filename) and S_ISSOCK(os.stat(filename).st_mode): + target = BytesIO() + output = KDBX.build_stream( + self.kdbx, + target, + password=self.password, + keyfile=self.keyfile, + transformed_key=transformed_key + ) + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: + target.seek(0) + sock.connect(filename) + sock.sendall(target.read()) + else: if exists(filename): with open(filename) as file: