keepass22pass.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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. import getopt, sys
  20. from subprocess import Popen, PIPE
  21. from xml.etree import ElementTree
  22. def pass_import_entry(path, data):
  23. """ Import new password entry to password-store using pass insert command """
  24. proc = Popen(['pass', 'insert', '--multiline', path], stdin=PIPE, stdout=PIPE)
  25. proc.communicate(data.encode('utf8'))
  26. proc.wait()
  27. def get_value(elements, node_text):
  28. for element in elements:
  29. for child in element.findall('Key'):
  30. if child.text == node_text:
  31. return element.find('Value').text
  32. return ''
  33. def path_for(element, path=''):
  34. """ Generate path name from elements title and current path """
  35. if element.tag == 'Entry':
  36. title = get_value(element.findall("String"), "Title")
  37. elif element.tag == 'Group':
  38. title = element.find('Name').text
  39. else: title = ''
  40. if path == '': return title
  41. else: return '/'.join([path, title])
  42. def password_data(element, path=''):
  43. """ Return password data and additional info if available from password entry element. """
  44. data = ""
  45. password = get_value(element.findall('String'), 'Password')
  46. if password is not None: data = password + "\n"
  47. else:
  48. print "[WARN] No password: %s" % path_for(element, path)
  49. for field in ['UserName', 'URL', 'Notes']:
  50. value = get_value(element, field)
  51. if value is not None and not len(value) == 0:
  52. data = "%s%s: %s\n" % (data, field, value)
  53. return data
  54. def import_entry(entries, element, path=''):
  55. element_path = path_for(element, path)
  56. if entries.has_key(element_path):
  57. print "[INFO] Duplicate needs merging: %s" % element_path
  58. existing_data = entries[element_path]
  59. data = "%s---------\nPassword: %s" % (existing_data, password_data(element))
  60. else:
  61. data = password_data(element, path)
  62. entries[element_path] = data
  63. def import_group(entries, element, path=''):
  64. """ Import all entries and sub-groups from given group """
  65. npath = path_for(element, path)
  66. for group in element.findall('Group'):
  67. import_group(entries, group, npath)
  68. for entry in element.findall('Entry'):
  69. import_entry(entries, entry, npath)
  70. def import_passwords(xml_file, root_path=None):
  71. """ Parse given Keepass2 XML file and import password groups from it """
  72. print "[>>>>] Importing passwords from file %s" % xml_file
  73. print "[INFO] Root path: %s" % root_path
  74. entries = dict()
  75. with open(xml_file) as xml:
  76. text = xml.read()
  77. xml_tree = ElementTree.XML(text)
  78. root = xml_tree.find('Root')
  79. root_group = root.find('Group')
  80. import_group(entries,root_group,'')
  81. if root_path is None: root_path = root_group.find('Name').text
  82. groups = root_group.findall('Group')
  83. for group in groups:
  84. import_group(entries, group, root_path)
  85. password_count = 0
  86. for path, data in sorted(entries.iteritems()):
  87. sys.stdout.write("[>>>>] Importing %s ... " % path.encode("utf-8"))
  88. pass_import_entry(path, data)
  89. sys.stdout.write("OK\n")
  90. password_count += 1
  91. print "[ OK ] Done. Imported %i passwords." % password_count
  92. def usage():
  93. """ Print usage """
  94. print "Usage: %s -f XML_FILE" % (sys.argv[0])
  95. print "Optional:"
  96. print " -r ROOT_PATH Different root path to use than the one in xml file, use \"\" for none"
  97. def main(argv):
  98. try:
  99. opts, args = getopt.gnu_getopt(argv, "f:r:")
  100. except getopt.GetoptError as err:
  101. print str(err)
  102. usage()
  103. sys.exit(2)
  104. xml_file = None
  105. root_path = None
  106. for opt, arg in opts:
  107. if opt in "-f":
  108. xml_file = arg
  109. if opt in "-r":
  110. root_path = arg
  111. if xml_file is not None:
  112. import_passwords(xml_file, root_path)
  113. else:
  114. usage()
  115. sys.exit(2)
  116. if __name__ == '__main__':
  117. main(sys.argv[1:])