rename keepass2 importer
This commit is contained in:
parent
970db84d03
commit
57790b6746
141
keepass2import.py
Normal file
141
keepass2import.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#! /usr/bin/env python2
|
||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
# Minor Modifications by Rasmus Steinke <rasi@xssn.at>
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
username = get_value(element, 'UserName')
|
||||||
|
url = get_value(element, 'URL')
|
||||||
|
notes = get_value(element, 'Notes')
|
||||||
|
data = "%sUserName: %s\n" % (data, username)
|
||||||
|
data = "%sURL: %s\n" % (data, url)
|
||||||
|
data = "%s%s\n" % (data, notes)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def import_entry(entries, element, path=''):
|
||||||
|
element_path = path_for(element, path)
|
||||||
|
if entries.has_key(element_path):
|
||||||
|
existing_data = entries[element_path]
|
||||||
|
data = (existing_data)
|
||||||
|
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')
|
||||||
|
import_group(entries,root_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:])
|
Loading…
Reference in New Issue
Block a user