[pass] [PATCH] Added alternative converter for keepassx stores, which does not do CamelCase and can slightly more elegantly handle entries with duplicate names.

Boi Sletterink boi at sletterink.nl
Wed Jun 10 11:36:01 CEST 2015


---
 contrib/importers/keepassx2pass-no_camel_case.py | 107 +++++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/contrib/importers/keepassx2pass-no_camel_case.py b/contrib/importers/keepassx2pass-no_camel_case.py
new file mode 100755
index 0000000..8d028b1
--- /dev/null
+++ b/contrib/importers/keepassx2pass-no_camel_case.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 Juhamatti Niemelä <iiska at iki.fi>. All Rights Reserved.
+# This file is licensed under the GPLv2+. Please see COPYING for more information.
+
+import sys
+import re
+import os
+
+from subprocess import Popen, PIPE
+from xml.etree import ElementTree
+
+
+def cleanTitle(title):
+    # make the title more command line friendly
+    
+    title = re.sub(" ", "_", title)
+    title = re.sub("'", "", title)
+    title = re.sub("\W", "-", title)
+
+    total_changes = 1
+    while total_changes > 0:
+        total_changes = 0
+        (title, changes) = re.subn("__+", "_", title)
+        total_changes = total_changes + changes
+        (title, changes) = re.subn("(--+|_-|-_)", "-", title)
+        total_changes = total_changes + changes
+
+    title = re.sub("[-_]+$", "", title)
+    title = re.sub("^[-_]+", "", title)
+
+    return title
+
+
+def pass_entry_exists(entry):
+    full_path = os.environ['HOME'] + '/.password-store' + entry + '.gpg'
+    print full_path + "\n"
+
+    try:
+        file = open(full_path);
+    except IOError as e:
+        return False
+
+    return True
+
+
+def path_for(element, path=''):
+    """ Generate path name from elements title and current path """
+    title_text = element.find('title').text
+    if title_text is None:
+        title_text = "unknown"
+    title = cleanTitle(title_text)
+
+    # Check for uniqueness, and make it unique if we have to
+    entry_path = '/'.join([path,title])
+    serial_number = 1
+    while pass_entry_exists(entry_path):
+       serial_number = serial_number + 1
+       entry_path = '/'.join([path, "{0}-{1:d}".format(title, serial_number)])
+
+    return entry_path
+
+
+def password_data(element):
+    """ Return password data and additional info if available from
+    password entry element. """
+    passwd = element.find('password').text
+    ret = passwd + "\n" if passwd else "\n"
+    for field in ['title','username', 'url', 'comment']:
+        fel = element.find(field)
+        children = [unicode(e.text or '') + unicode(e.tail or '') for e in list(fel)]
+        if len(children) > 0:
+            children.insert(0, '')
+        text = (fel.text or '') + "\n".join(children)
+        if len(text) > 0:
+            ret = "%s%s: %s\n" % (ret, fel.tag, text)
+    return ret
+
+
+def import_entry(element, path=''):
+    """ Import new password entry to password-store using pass insert
+    command """
+    print "Importing " + path_for(element, path)
+    proc = Popen(['pass', 'insert', '--multiline', '--force',
+                  path_for(element, path)],
+              stdin=PIPE, stdout=PIPE)
+    proc.communicate(password_data(element).encode('utf8'))
+    proc.wait()
+
+
+def import_group(element, path=''):
+    """ Import all entries and sub-groups from given group """
+    npath = path_for(element, path)
+    for group in element.findall('group'):
+        import_group(group, npath)
+    for entry in element.findall('entry'):
+        import_entry(entry, npath)
+
+
+def main(xml_file):
+    """ Parse given KeepassX XML file and import password groups from it """
+    for group in ElementTree.parse(xml_file).findall('group'):
+        import_group(group)
+
+if __name__ == '__main__':
+    main(sys.argv[1])
-- 
2.1.4



More information about the Password-Store mailing list