password-store

Simple password manager using gpg and ordinary unix directories
git clone https://git.zx2c4.com/password-store
Log | Files | Refs | README | LICENSE

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:])