keepass2pass.py (4686B)
1 #! /usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # 4 # Copyright (C) 2013 Stefan Simroth <stefan.simroth@gmail.com>. All Rights Reserved. 5 # Based on the script for KeepassX by Juhamatti Niemelä <iiska@iki.fi>. 6 # This file is licensed under the GPLv2+. Please see COPYING for more information. 7 # 8 # Usage: 9 # ./keepass2pass.py -f export.xml 10 # By default, takes the name of the root element and puts all passwords in there, but you can disable this: 11 # ./keepass2pass.py -f export.xml -r "" 12 # Or you can use another root folder: 13 # ./keepass2pass.py -f export.xml -r foo 14 # 15 # Features: 16 # * This script can handle duplicates and will merge them. 17 # * Besides the password also the fields 'UserName', 'URL' and 'Notes' (comment) will be inserted. 18 # * You get a warning if an entry has no password, but it will still insert it. 19 20 import getopt, sys 21 from subprocess import Popen, PIPE 22 from xml.etree import ElementTree 23 24 25 def pass_import_entry(path, data): 26 """ Import new password entry to password-store using pass insert command """ 27 proc = Popen(['pass', 'insert', '--multiline', path], stdin=PIPE, stdout=PIPE) 28 proc.communicate(data.encode('utf8')) 29 proc.wait() 30 31 32 def get_value(elements, node_text): 33 for element in elements: 34 for child in element.findall('Key'): 35 if child.text == node_text: 36 return element.find('Value').text 37 return '' 38 39 def path_for(element, path=''): 40 """ Generate path name from elements title and current path """ 41 if element.tag == 'Entry': 42 title = get_value(element.findall("String"), "Title") 43 elif element.tag == 'Group': 44 title = element.find('Name').text 45 else: title = '' 46 47 if path == '': return title 48 else: return '/'.join([path, title]) 49 50 def password_data(element, path=''): 51 """ Return password data and additional info if available from password entry element. """ 52 data = "" 53 password = get_value(element.findall('String'), 'Password') 54 if password is not None: data = password + "\n" 55 else: 56 print "[WARN] No password: %s" % path_for(element, path) 57 58 for field in ['UserName', 'URL', 'Notes']: 59 value = get_value(element, field) 60 if value is not None and not len(value) == 0: 61 data = "%s%s: %s\n" % (data, field, value) 62 return data 63 64 def import_entry(entries, element, path=''): 65 element_path = path_for(element, path) 66 if entries.has_key(element_path): 67 print "[INFO] Duplicate needs merging: %s" % element_path 68 existing_data = entries[element_path] 69 data = "%s---------\nPassword: %s" % (existing_data, password_data(element)) 70 else: 71 data = password_data(element, path) 72 73 entries[element_path] = data 74 75 def import_group(entries, element, path='', npath=None): 76 """ Import all entries and sub-groups from given group """ 77 if npath is None: 78 npath = path_for(element, path) 79 for group in element.findall('Group'): 80 import_group(entries, group, npath) 81 for entry in element.findall('Entry'): 82 import_entry(entries, entry, npath) 83 84 def import_passwords(xml_file, root_path=None): 85 """ Parse given Keepass2 XML file and import password groups from it """ 86 print "[>>>>] Importing passwords from file %s" % xml_file 87 print "[INFO] Root path: %s" % root_path 88 entries = dict() 89 with open(xml_file) as xml: 90 text = xml.read() 91 xml_tree = ElementTree.XML(text) 92 root = xml_tree.find('Root') 93 root_group = root.find('Group') 94 import_group(entries, root_group, '', root_path) 95 password_count = 0 96 for path, data in sorted(entries.iteritems()): 97 sys.stdout.write("[>>>>] Importing %s ... " % path.encode("utf-8")) 98 pass_import_entry(path, data) 99 sys.stdout.write("OK\n") 100 password_count += 1 101 102 print "[ OK ] Done. Imported %i passwords." % password_count 103 104 105 def usage(): 106 """ Print usage """ 107 print "Usage: %s -f XML_FILE" % (sys.argv[0]) 108 print "Optional:" 109 print " -r ROOT_PATH Different root path to use than the one in xml file, use \"\" for none" 110 111 112 def main(argv): 113 try: 114 opts, args = getopt.gnu_getopt(argv, "f:r:") 115 except getopt.GetoptError as err: 116 print str(err) 117 usage() 118 sys.exit(2) 119 120 xml_file = None 121 root_path = None 122 123 for opt, arg in opts: 124 if opt in "-f": 125 xml_file = arg 126 if opt in "-r": 127 root_path = arg 128 129 if xml_file is not None: 130 import_passwords(xml_file, root_path) 131 else: 132 usage() 133 sys.exit(2) 134 135 if __name__ == '__main__': 136 main(sys.argv[1:])