diff options
author | Stefan Simroth <stefan.simroth@gmail.com> | 2013-05-18 21:23:16 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2013-05-19 02:00:26 +0200 |
commit | 7797980519ad8938e8290503ce721ef5f194c01f (patch) | |
tree | f680c8848922d40fb1a80c4741e79965b148cef6 | |
parent | 659dabebe363e794b22d8e6b5d15854831ec847e (diff) | |
download | pass-7797980519ad8938e8290503ce721ef5f194c01f.tar.gz pass-7797980519ad8938e8290503ce721ef5f194c01f.tar.bz2 pass-7797980519ad8938e8290503ce721ef5f194c01f.zip |
Add Keepass2 import script.
Diffstat (limited to '')
-rwxr-xr-x | contrib/keepass2pass.py | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/contrib/keepass2pass.py b/contrib/keepass2pass.py new file mode 100755 index 0000000..16764bd --- /dev/null +++ b/contrib/keepass2pass.py @@ -0,0 +1,138 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Stefan Simroth <stefan.simroth@gmail.com>. All Rights Reserved. +# Based on the script for KeepassX by Juhamatti Niemelä <iiska@iki.fi>. +# This file is licensed under the GPLv2+. Please see COPYING for more information. +# +# Usage: +# ./keepass2pass.py -f export.xml +# By default, takes the name of the root element and puts all passwords in there, but you can disable this: +# ./keepass2pass.py -f export.xml -r "" +# Or you can use another root folder: +# ./keepass2pass.py -f export.xml -r foo +# +# Features: +# * This script can handle duplicates and will merge them. +# * Besides the password also the fields 'UserName', 'URL' and 'Notes' (comment) will be inserted. +# * You get a warning if an entry has no password, but it will still insert it. + +import getopt, sys +from subprocess import Popen, PIPE +from xml.etree import ElementTree + + +def pass_import_entry(path, data): + """ Import new password entry to password-store using pass insert command """ + proc = Popen(['pass', 'insert', '--multiline', path], stdin=PIPE, stdout=PIPE) + proc.communicate(data.encode('utf8')) + proc.wait() + + +def get_value(elements, node_text): + for element in elements: + for child in element.findall('Key'): + if child.text == node_text: + return element.find('Value').text + return '' + +def path_for(element, path=''): + """ Generate path name from elements title and current path """ + if element.tag == 'Entry': + title = get_value(element.findall("String"), "Title") + elif element.tag == 'Group': + title = element.find('Name').text + else: title = '' + + if path == '': return title + else: return '/'.join([path, title]) + +def password_data(element, path=''): + """ Return password data and additional info if available from password entry element. """ + data = "" + password = get_value(element.findall('String'), 'Password') + if password is not None: data = password + "\n" + else: + print "[WARN] No password: %s" % path_for(element, path) + + for field in ['UserName', 'URL', 'Notes']: + value = get_value(element, field) + if value is not None and not len(value) == 0: + data = "%s%s: %s\n" % (data, field, value) + return data + +def import_entry(entries, element, path=''): + element_path = path_for(element, path) + if entries.has_key(element_path): + print "[INFO] Duplicate needs merging: %s" % element_path + existing_data = entries[element_path] + data = "%s---------\nPassword: %s" % (existing_data, password_data(element)) + else: + data = password_data(element, path) + + entries[element_path] = data + +def import_group(entries, element, path=''): + """ Import all entries and sub-groups from given group """ + npath = path_for(element, path) + for group in element.findall('Group'): + import_group(entries, group, npath) + for entry in element.findall('Entry'): + import_entry(entries, entry, npath) + +def import_passwords(xml_file, root_path=None): + """ Parse given Keepass2 XML file and import password groups from it """ + print "[>>>>] Importing passwords from file %s" % xml_file + print "[INFO] Root path: %s" % root_path + entries = dict() + with open(xml_file) as xml: + text = xml.read() + xml_tree = ElementTree.XML(text) + root = xml_tree.find('Root') + root_group = root.find('Group') + if root_path is None: root_path = root_group.find('Name').text + groups = root_group.findall('Group') + for group in groups: + import_group(entries, group, root_path) + password_count = 0 + for path, data in sorted(entries.iteritems()): + sys.stdout.write("[>>>>] Importing %s ... " % path.encode("utf-8")) + pass_import_entry(path, data) + sys.stdout.write("OK\n") + password_count += 1 + + print "[ OK ] Done. Imported %i passwords." % password_count + + +def usage(): + """ Print usage """ + print "Usage: %s -f XML_FILE" % (sys.argv[0]) + print "Optional:" + print " -r ROOT_PATH Different root path to use than the one in xml file, use \"\" for none" + + +def main(argv): + try: + opts, args = getopt.gnu_getopt(argv, "f:r:") + except getopt.GetoptError as err: + print str(err) + usage() + sys.exit(2) + + xml_file = None + root_path = None + + for opt, arg in opts: + if opt in "-f": + xml_file = arg + if opt in "-r": + root_path = arg + + if xml_file is not None: + import_passwords(xml_file, root_path) + else: + usage() + sys.exit(2) + +if __name__ == '__main__': + main(sys.argv[1:]) |