From 47fed2c5d47a03fad7b91bfb890eed257e9c1b2d Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sat, 22 Mar 2014 12:01:52 -0600 Subject: Makefile: do not use recursion and organize --- contrib/fpm2pass.pl | 79 ---------------- contrib/gorilla2pass.rb | 76 ---------------- contrib/importers/fpm2pass.pl | 79 ++++++++++++++++ contrib/importers/gorilla2pass.rb | 76 ++++++++++++++++ contrib/importers/kedpm2pass.py | 52 +++++++++++ contrib/importers/keepass2pass.py | 139 ++++++++++++++++++++++++++++ contrib/importers/keepassx2pass.py | 76 ++++++++++++++++ contrib/importers/lastpass2pass.rb | 131 ++++++++++++++++++++++++++ contrib/importers/pwsafe2pass.sh | 30 ++++++ contrib/importers/revelation2pass.py | 172 +++++++++++++++++++++++++++++++++++ contrib/kedpm2pass.py | 52 ----------- contrib/keepass2pass.py | 139 ---------------------------- contrib/keepassx2pass.py | 76 ---------------- contrib/lastpass2pass.rb | 131 -------------------------- contrib/pass.bash-completion | 87 ------------------ contrib/pass.fish-completion | 104 --------------------- contrib/pass.zsh-completion | 116 ----------------------- contrib/pwsafe2pass.sh | 30 ------ contrib/revelation2pass.py | 172 ----------------------------------- 19 files changed, 755 insertions(+), 1062 deletions(-) delete mode 100755 contrib/fpm2pass.pl delete mode 100755 contrib/gorilla2pass.rb create mode 100755 contrib/importers/fpm2pass.pl create mode 100755 contrib/importers/gorilla2pass.rb create mode 100755 contrib/importers/kedpm2pass.py create mode 100755 contrib/importers/keepass2pass.py create mode 100755 contrib/importers/keepassx2pass.py create mode 100755 contrib/importers/lastpass2pass.rb create mode 100755 contrib/importers/pwsafe2pass.sh create mode 100755 contrib/importers/revelation2pass.py delete mode 100755 contrib/kedpm2pass.py delete mode 100755 contrib/keepass2pass.py delete mode 100755 contrib/keepassx2pass.py delete mode 100755 contrib/lastpass2pass.rb delete mode 100644 contrib/pass.bash-completion delete mode 100644 contrib/pass.fish-completion delete mode 100644 contrib/pass.zsh-completion delete mode 100755 contrib/pwsafe2pass.sh delete mode 100755 contrib/revelation2pass.py (limited to 'contrib') diff --git a/contrib/fpm2pass.pl b/contrib/fpm2pass.pl deleted file mode 100755 index d1a0908..0000000 --- a/contrib/fpm2pass.pl +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/perl - -# Copyright (C) 2012 Jeffrey Ratcliffe . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -use warnings; -use strict; -use XML::Simple; -use Getopt::Long; -use Pod::Usage; - -my ($help, $man); -my @args = ('help' => \$help, - 'man' => \$man,); -GetOptions (@args) or pod2usage(2); -pod2usage(1) if ($help); -pod2usage(-exitstatus => 0, -verbose => 2) if $man; -pod2usage( - -msg => "Syntax error: must specify a file to read.", - -exitval => 2, - -verbose => 1 -) - if (@ARGV != 1); - -# Grab the XML to a perl structure -my $xs = XML::Simple->new(); -my $doc = $xs->XMLin(shift); - -for (@{$doc->{PasswordList}{PasswordItem}}) { - my $name; - if (ref($_->{category}) eq 'HASH') { - $name = escape($_->{title}); - } - else { - $name = escape($_->{category})."/".escape($_->{title}); - } - my $contents = ''; - $contents .= "$_->{password}\n" unless (ref($_->{password}) eq 'HASH'); - $contents .= "user $_->{user}\n" unless (ref($_->{user}) eq 'HASH'); - $contents .= "url $_->{url}\n" unless (ref($_->{url}) eq 'HASH'); - unless (ref($_->{notes}) eq 'HASH') { - $_->{notes} =~ s/\n/\n /g; - $contents .= "notes:\n $_->{notes}\n"; - } - my $cmd = "pass insert -f -m $name"; - my $pid = open(my $fh, "| $cmd") or die "Couldn't fork: $!\n"; - print $fh $contents; - close $fh; -} - -# escape inverted commas, spaces, ampersands and brackets -sub escape { - my ($s) = @_; - $s =~ s/\//-/g; - $s =~ s/(['\(\) &])/\\$1/g; - return $s; -} - -=head1 NAME - - fpm2pass.pl - imports an .xml exported by fpm2 into pass - -=head1 SYNOPSIS - -=head1 USAGE - - fpm2pass.pl [--help] [--man] - -The following options are available: - -=over - -=item --help - -=item --man - -=back - -=cut diff --git a/contrib/gorilla2pass.rb b/contrib/gorilla2pass.rb deleted file mode 100755 index bf168a7..0000000 --- a/contrib/gorilla2pass.rb +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env ruby - -# Copyright (C) 2013 David Sklar . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -entries = {} - -class HashCounter - - def initialize - @h = Hash.new {|h,k| h[k] = 2 } - end - - def get(k) - v = @h[k] - @h[k] = v + 1 - v - end -end - -hc = HashCounter.new - -$stdin.each do |line| - uuid, group, title, url, user, password, notes = line.strip.split(',') - next if uuid == "uuid" - - # check for missing group - # check for missing title - - prefix = "#{group}/#{title}".gsub(/[\s\'\"()!]/,'') - - - if user && user.length > 0 - entries["#{prefix}/user"] = user - end - if url && url.length > 0 - entries["#{prefix}/url"] = url - end - if password && password.length > 0 - entries["#{prefix}/password"] = password - end - if notes && notes.length > 0 - entries["#{prefix}/notes"] = notes.gsub('\n',"\n").strip - end -end - -entries.keys.each do |k| - if k =~ /^(.+?)-merged\d{4}-\d\d-\d\d\d\d:\d\d:\d\d(\/.+)$/ - other = $1 + $2 - if entries.has_key?(other) - if entries[k] == entries[other] - entries.delete(k) - else - i = hc.get(other) - entries["#{other}#{i}"] = entries[k] - entries.delete(k) - end - else - entries[other] = entries[k] - entries.delete(k) - end - end -end - -pass_top_level = "Gorilla" -entries.keys.each do |k| - print "#{k}...(#{entries[k]})..." - IO.popen("pass insert -e -f '#{pass_top_level}/#{k}' > /dev/null", 'w') do |io| - io.puts entries[k] + "\n" - end - if $? == 0 - puts " done!" - else - puts " error!" - end -end diff --git a/contrib/importers/fpm2pass.pl b/contrib/importers/fpm2pass.pl new file mode 100755 index 0000000..d1a0908 --- /dev/null +++ b/contrib/importers/fpm2pass.pl @@ -0,0 +1,79 @@ +#!/usr/bin/perl + +# Copyright (C) 2012 Jeffrey Ratcliffe . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +use warnings; +use strict; +use XML::Simple; +use Getopt::Long; +use Pod::Usage; + +my ($help, $man); +my @args = ('help' => \$help, + 'man' => \$man,); +GetOptions (@args) or pod2usage(2); +pod2usage(1) if ($help); +pod2usage(-exitstatus => 0, -verbose => 2) if $man; +pod2usage( + -msg => "Syntax error: must specify a file to read.", + -exitval => 2, + -verbose => 1 +) + if (@ARGV != 1); + +# Grab the XML to a perl structure +my $xs = XML::Simple->new(); +my $doc = $xs->XMLin(shift); + +for (@{$doc->{PasswordList}{PasswordItem}}) { + my $name; + if (ref($_->{category}) eq 'HASH') { + $name = escape($_->{title}); + } + else { + $name = escape($_->{category})."/".escape($_->{title}); + } + my $contents = ''; + $contents .= "$_->{password}\n" unless (ref($_->{password}) eq 'HASH'); + $contents .= "user $_->{user}\n" unless (ref($_->{user}) eq 'HASH'); + $contents .= "url $_->{url}\n" unless (ref($_->{url}) eq 'HASH'); + unless (ref($_->{notes}) eq 'HASH') { + $_->{notes} =~ s/\n/\n /g; + $contents .= "notes:\n $_->{notes}\n"; + } + my $cmd = "pass insert -f -m $name"; + my $pid = open(my $fh, "| $cmd") or die "Couldn't fork: $!\n"; + print $fh $contents; + close $fh; +} + +# escape inverted commas, spaces, ampersands and brackets +sub escape { + my ($s) = @_; + $s =~ s/\//-/g; + $s =~ s/(['\(\) &])/\\$1/g; + return $s; +} + +=head1 NAME + + fpm2pass.pl - imports an .xml exported by fpm2 into pass + +=head1 SYNOPSIS + +=head1 USAGE + + fpm2pass.pl [--help] [--man] + +The following options are available: + +=over + +=item --help + +=item --man + +=back + +=cut diff --git a/contrib/importers/gorilla2pass.rb b/contrib/importers/gorilla2pass.rb new file mode 100755 index 0000000..bf168a7 --- /dev/null +++ b/contrib/importers/gorilla2pass.rb @@ -0,0 +1,76 @@ +#!/usr/bin/env ruby + +# Copyright (C) 2013 David Sklar . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +entries = {} + +class HashCounter + + def initialize + @h = Hash.new {|h,k| h[k] = 2 } + end + + def get(k) + v = @h[k] + @h[k] = v + 1 + v + end +end + +hc = HashCounter.new + +$stdin.each do |line| + uuid, group, title, url, user, password, notes = line.strip.split(',') + next if uuid == "uuid" + + # check for missing group + # check for missing title + + prefix = "#{group}/#{title}".gsub(/[\s\'\"()!]/,'') + + + if user && user.length > 0 + entries["#{prefix}/user"] = user + end + if url && url.length > 0 + entries["#{prefix}/url"] = url + end + if password && password.length > 0 + entries["#{prefix}/password"] = password + end + if notes && notes.length > 0 + entries["#{prefix}/notes"] = notes.gsub('\n',"\n").strip + end +end + +entries.keys.each do |k| + if k =~ /^(.+?)-merged\d{4}-\d\d-\d\d\d\d:\d\d:\d\d(\/.+)$/ + other = $1 + $2 + if entries.has_key?(other) + if entries[k] == entries[other] + entries.delete(k) + else + i = hc.get(other) + entries["#{other}#{i}"] = entries[k] + entries.delete(k) + end + else + entries[other] = entries[k] + entries.delete(k) + end + end +end + +pass_top_level = "Gorilla" +entries.keys.each do |k| + print "#{k}...(#{entries[k]})..." + IO.popen("pass insert -e -f '#{pass_top_level}/#{k}' > /dev/null", 'w') do |io| + io.puts entries[k] + "\n" + end + if $? == 0 + puts " done!" + else + puts " error!" + end +end diff --git a/contrib/importers/kedpm2pass.py b/contrib/importers/kedpm2pass.py new file mode 100755 index 0000000..b79cc8b --- /dev/null +++ b/contrib/importers/kedpm2pass.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 Antoine Beaupré . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. +# +# To double-check your import worked: +# grep Path passwords | sed 's#^Path: ##;s/$/.gpg/' | sort > listpaths +# (cd ~/.password-store/ ; find -type f ) | sort | diff -u - listpaths + +import re +import fileinput + +import sys # for exit + +import subprocess + +def insert(d): + path = d['Path'] + del d['Path'] + print "inserting " + path + content = d['Password'] + "\n" + del d['Password'] + for k, v in d.iteritems(): + content += "%s: %s\n" % (k, v) + del d + cmd = ["pass", "insert", "--force", "--multiline", path] + process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate(content) + retcode = process.wait() + if retcode: + print 'command "%s" failed with exit code %d: %s' % (" ".join(cmd), retcode, stdout + stderr) + sys.exit(1); + +d = None +for line in fileinput.input(): + if line == "\n": + continue + match = re.match("(\w+): (.*)$", line) + if match: + if match.group(1) == 'Path': + if d is not None: + insert(d) + else: + d = {} + d[match.group(1)] = match.group(2) + #print "found field: %s => %s" % (match.group(1), match.group(2)) + else: + print "warning: no match found on line: *%s*" % line + +if d is not None: + insert(d) diff --git a/contrib/importers/keepass2pass.py b/contrib/importers/keepass2pass.py new file mode 100755 index 0000000..80a2ad9 --- /dev/null +++ b/contrib/importers/keepass2pass.py @@ -0,0 +1,139 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Stefan Simroth . All Rights Reserved. +# Based on the script for KeepassX by Juhamatti Niemelä . +# 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') + 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:]) diff --git a/contrib/importers/keepassx2pass.py b/contrib/importers/keepassx2pass.py new file mode 100755 index 0000000..dc4b1e5 --- /dev/null +++ b/contrib/importers/keepassx2pass.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2012 Juhamatti Niemelä . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +import sys +import re + +from subprocess import Popen, PIPE +from xml.etree import ElementTree + +def space_to_camelcase(value): + output = "" + first_word_passed = False + for word in value.split(" "): + if not word: + output += "_" + continue + if first_word_passed: + output += word.capitalize() + else: + output += word.lower() + first_word_passed = True + return output + +def cleanTitle(title): + # make the title more command line friendly + title = re.sub("(\\|\||\(|\))", "-", title) + title = re.sub("-$", "", title) + title = re.sub("\@", "At", title) + title = re.sub("'", "", title) + return title + +def path_for(element, path=''): + """ Generate path name from elements title and current path """ + title = cleanTitle(space_to_camelcase(element.find('title').text)) + return '/'.join([path, title]) + +def password_data(element): + """ Return password data and additional info if available from + password entry element. """ + passwd = element.find('password').text + ret = passwd + "\n" if passwd else "\n" + for field in ['username', 'url', 'comment']: + fel = element.find(field) + if fel.text is not None: + ret = "%s%s: %s\n" % (ret, fel.tag, fel.text) + return ret + +def import_entry(element, path=''): + """ Import new password entry to password-store using pass insert + command """ + proc = Popen(['pass', 'insert', '--multiline', '--force', + path_for(element, path)], + stdin=PIPE, stdout=PIPE) + proc.communicate(password_data(element).encode('utf8')) + proc.wait() + +def import_group(element, path=''): + """ Import all entries and sub-groups from given group """ + npath = path_for(element, path) + for group in element.findall('group'): + import_group(group, npath) + for entry in element.findall('entry'): + import_entry(entry, npath) + + +def main(xml_file): + """ Parse given KeepassX XML file and import password groups from it """ + with open(xml_file) as xml: + for group in ElementTree.XML(xml.read()).findall('group'): + import_group(group) + +if __name__ == '__main__': + main(sys.argv[1]) diff --git a/contrib/importers/lastpass2pass.rb b/contrib/importers/lastpass2pass.rb new file mode 100755 index 0000000..41a2a29 --- /dev/null +++ b/contrib/importers/lastpass2pass.rb @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby + +# Copyright (C) 2012 Alex Sayers . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +# LastPass Importer +# +# Reads CSV files exported from LastPass and imports them into pass. + +# Usage: +# +# Go to lastpass.com and sign in. Next click on your username in the top-right +# corner. In the drop-down meny that appears, click "Export". After filling in +# your details again, copy the text and save it somewhere on your disk. Make sure +# you copy the whole thing, and resist the temptation to "Save Page As" - the +# script doesn't like HTML. +# +# Fire up a terminal and run the script, passing the file you saved as an argument. +# It should look something like this: +# +#$ ./lastpass2pass.rb path/to/passwords_file.csv + +# Parse flags +require 'optparse' +optparse = OptionParser.new do |opts| + opts.banner = "Usage: #{$0} [options] filename" + + FORCE = false + opts.on("-f", "--force", "Overwrite existing records") { FORCE = true } + DEFAULT_GROUP = "" + opts.on("-d", "--default GROUP", "Place uncategorised records into GROUP") { |group| DEFAULT_GROUP = group } + opts.on("-h", "--help", "Display this screen") { puts opts; exit } + + opts.parse! +end + +# Check for a filename +if ARGV.empty? + puts optparse + exit 0 +end + +# Get filename of csv file +filename = ARGV.join(" ") +puts "Reading '#{filename}'..." + + +class Record + def initialize name, url, username, password, extra, grouping, fav + @name, @url, @username, @password, @extra, @grouping, @fav = name, url, username, password, extra, grouping, fav + end + + def name + s = "" + s << @grouping + "/" unless @grouping.empty? + s << @name + s.gsub(/ /, "_").gsub(/'/, "") + end + + def to_s + s = "" + s << "#{@password}\n---\n" + s << "#{@grouping} / " unless @grouping.empty? + s << "#{@name}\n" + s << "username: #{@username}\n" unless @username.empty? + s << "password: #{@password}\n" unless @password.empty? + s << "url: #{@url}\n" unless @url == "http://sn" + s << "#{@extra}\n" unless @extra.nil? + return s + end +end + +# Extract individual records +entries = [] +entry = "" +begin + file = File.open(filename) + file.each do |line| + if line =~ /^http/ + entries.push(entry) + entry = "" + end + entry += line + end + entries.push(entry) + entries.shift + puts "#{entries.length} records found!" +rescue + puts "Couldn't find #{filename}!" + exit 1 +end + +# Parse records and create Record objects +records = [] +entries.each do |e| + args = e.split(",") + url = args.shift + username = args.shift + password = args.shift + fav = args.pop + grouping = args.pop + grouping = DEFAULT_GROUP if grouping.empty? + name = args.pop + extra = args.join(",")[1...-1] + + records << Record.new(name, url, username, password, extra, grouping, fav) +end +puts "Records parsed: #{records.length}" + +successful = 0 +errors = [] +records.each do |r| + print "Creating record #{r.name}..." + IO.popen("pass insert -m#{"f" if FORCE} '#{r.name}' > /dev/null", 'w') do |io| + io.puts r + end + if $? == 0 + puts " done!" + successful += 1 + else + puts " error!" + errors << r + end +end +puts "#{successful} records successfully imported!" + +if errors + puts "There were #{errors.length} errors:" + errors.each { |e| print e.name + (e == errors.last ? ".\n" : ", ")} + puts "These probably occurred because an identically-named record already existed, or because there were multiple entries with the same name in the csv file." +end diff --git a/contrib/importers/pwsafe2pass.sh b/contrib/importers/pwsafe2pass.sh new file mode 100755 index 0000000..c29bb3f --- /dev/null +++ b/contrib/importers/pwsafe2pass.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# Copyright (C) 2013 Tom Hendrikx . All Rights Reserved. +# This file is licensed under the GPLv2+. Please see COPYING for more information. + +export=$1 + +IFS=" " # tab character +cat "$export" | while read uuid group name login passwd notes; do + test "$uuid" = "# passwordsafe version 2.0 database" && continue + test "$uuid" = "uuid" && continue + test "$name" = '""' && continue; + + group="$(echo $group | cut -d'"' -f2)" + login="$(echo $login | cut -d'"' -f2)" + passwd="$(echo $passwd | cut -d'"' -f2)" + name="$(echo $name | cut -d'"' -f2)" + + # cleanup + test "${name:0:4}" = "http" && name="$(echo $name | cut -d'/' -f3)" + test "${name:0:4}" = "www." && name="$(echo $name | cut -c 5-)" + + entry="" + test -n "$login" && entry="${entry}login: $login\n" + test -n "$passwd" && entry="${entry}pass: $passwd\n" + test -n "$group" && entry="${entry}group: $group\n" + + echo Adding entry for $name: + echo -e $entry | pass insert --multiline --force "$name" + test $? && echo "Added!" +done diff --git a/contrib/importers/revelation2pass.py b/contrib/importers/revelation2pass.py new file mode 100755 index 0000000..f04c1a8 --- /dev/null +++ b/contrib/importers/revelation2pass.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2013 Emanuele Aina . All Rights Reserved. +# Copyright (C) 2011 Toni Corvera. All Rights Reserved. +# This file is licensed under the BSD 2-clause license: +# http://www.opensource.org/licenses/BSD-2-Clause +# +# Import script for the Revelation password manager: +# http://revelation.olasagasti.info/ +# Heavily based on the Relevation command line tool: +# http://p.outlyer.net/relevation/ + +import os, sys, argparse, zlib, getpass, traceback +from subprocess import Popen, PIPE, STDOUT, CalledProcessError +from collections import OrderedDict +try: + from lxml import etree +except ImportError: + from xml.etree import ElementTree as etree + +USE_PYCRYPTO = True +try: + from Crypto.Cipher import AES +except ImportError: + USE_PYCRYPTO = False + try: + from crypto.cipher import rijndael, cbc + from crypto.cipher.base import noPadding + except ImportError: + sys.stderr.write('Either PyCrypto or cryptopy are required\n') + raise + +def path_for(element, path=None): + """ Generate path name from elements name and current path """ + name = element.find('name').text + name = name.replace('/', '-').replace('\\', '-') + path = path if path else '' + return os.path.join(path, name) + +def format_password_data(data): + """ Format the secret data that will be handed to Pass in multi-line mode: + $password + $fieldname: $fielddata + ... + $multi_line_notes_with_leading_spaces""" + password = data.pop('password', None) or '' + ret = password + '\n' + notes = data.pop('notes', None) + for label, text in data.iteritems(): + ret += label + ': ' + text + '\n' + if notes: + ret += ' ' + notes.replace('\n', '\n ').strip() + '\n' + return ret + +def password_data(element): + """ Return password data and additional info if available from + password entry element. """ + data = OrderedDict() + data['password'] = element.find('field[@id="generic-password"]').text + data['type'] = element.attrib['type'] + for field in element.findall('field'): + field_id = field.attrib['id'] + if field_id == 'generic-password': + continue + if field.text is not None: + data[field_id] = field.text + for tag in ('description', 'notes'): + field = element.find(tag) + if field is not None and field.text: + data[tag] = field.text + return format_password_data(data) + + +def import_entry(element, path=None, verbose=0): + """ Import new password entry to password-store using pass insert + command """ + cmd = ['pass', 'insert', '--multiline', '--force', path_for(element, path)] + if verbose: + print 'cmd:\n ' + ' '.join(cmd) + proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + stdin = password_data(element).encode('utf8') + if verbose: + print 'input:\n ' + stdin.replace('\n', '\n ').strip() + stdout, _ = proc.communicate(stdin) + retcode = proc.poll() + if retcode: + raise CalledProcessError(retcode, cmd, output=stdout) + +def import_folder(element, path=None, verbose=0): + path = path_for(element, path) + import_subentries(element, path, verbose) + +def import_subentries(element, path=None, verbose=0): + """ Import all sub entries of the current folder element """ + for entry in element.findall('entry'): + if entry.attrib['type'] == 'folder': + import_folder(entry, path, verbose) + else: + import_entry(entry, path, verbose) + +def decrypt_gz(key, cipher_text): + ''' Decrypt cipher_text using key. + decrypt(str, str) -> cleartext (gzipped xml) + + This function will use the underlying, available, cipher module. + ''' + if USE_PYCRYPTO: + # Extract IV + c = AES.new(key) + iv = c.decrypt(cipher_text[12:28]) + # Decrypt data, CBC mode + c = AES.new(key, AES.MODE_CBC, iv) + ct = c.decrypt(cipher_text[28:]) + else: + # Extract IV + c = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) + iv = c.decrypt(cipher_text[12:28]) + # Decrypt data, CBC mode + bc = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) + c = cbc.CBC(bc, padding=noPadding()) + ct = c.decrypt(cipher_text[28:], iv=iv) + return ct + +def main(datafile, verbose=False): + f = None + with open(datafile, "rb") as f: + # Encrypted data + data = f.read() + password = getpass.getpass() + # Pad password + password += (chr(0) * (32 - len(password))) + # Decrypt. Decrypted data is compressed + cleardata_gz = decrypt_gz(password, data) + # Length of data padding + padlen = ord(cleardata_gz[-1]) + # Decompress actual data (15 is wbits [ref3] DON'T CHANGE, 2**15 is the (initial) buf size) + xmldata = zlib.decompress(cleardata_gz[:-padlen], 15, 2**15) + root = etree.fromstring(xmldata) + import_subentries(root, verbose=verbose) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--verbose', '-v', action='count') + parser.add_argument('FILE', help="the file storing the Revelation passwords") + args = parser.parse_args() + + def err(s): + sys.stderr.write(s+'\n') + + try: + main(args.FILE, verbose=args.verbose) + except KeyboardInterrupt: + if args.verbose: + traceback.print_exc() + err(str(e)) + except zlib.error: + err('Failed to decompress decrypted data. Wrong password?') + sys.exit(os.EX_DATAERR) + except CalledProcessError as e: + if args.verbose: + traceback.print_exc() + print 'output:\n ' + e.output.replace('\n', '\n ').strip() + else: + err('CalledProcessError: ' + str(e)) + sys.exit(os.EX_IOERR) + except IOError as e: + if args.verbose: + traceback.print_exc() + else: + err('IOError: ' + str(e)) + sys.exit(os.EX_IOERR) diff --git a/contrib/kedpm2pass.py b/contrib/kedpm2pass.py deleted file mode 100755 index b79cc8b..0000000 --- a/contrib/kedpm2pass.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2012 Antoine Beaupré . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. -# -# To double-check your import worked: -# grep Path passwords | sed 's#^Path: ##;s/$/.gpg/' | sort > listpaths -# (cd ~/.password-store/ ; find -type f ) | sort | diff -u - listpaths - -import re -import fileinput - -import sys # for exit - -import subprocess - -def insert(d): - path = d['Path'] - del d['Path'] - print "inserting " + path - content = d['Password'] + "\n" - del d['Password'] - for k, v in d.iteritems(): - content += "%s: %s\n" % (k, v) - del d - cmd = ["pass", "insert", "--force", "--multiline", path] - process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = process.communicate(content) - retcode = process.wait() - if retcode: - print 'command "%s" failed with exit code %d: %s' % (" ".join(cmd), retcode, stdout + stderr) - sys.exit(1); - -d = None -for line in fileinput.input(): - if line == "\n": - continue - match = re.match("(\w+): (.*)$", line) - if match: - if match.group(1) == 'Path': - if d is not None: - insert(d) - else: - d = {} - d[match.group(1)] = match.group(2) - #print "found field: %s => %s" % (match.group(1), match.group(2)) - else: - print "warning: no match found on line: *%s*" % line - -if d is not None: - insert(d) diff --git a/contrib/keepass2pass.py b/contrib/keepass2pass.py deleted file mode 100755 index 80a2ad9..0000000 --- a/contrib/keepass2pass.py +++ /dev/null @@ -1,139 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013 Stefan Simroth . All Rights Reserved. -# Based on the script for KeepassX by Juhamatti Niemelä . -# 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') - 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:]) diff --git a/contrib/keepassx2pass.py b/contrib/keepassx2pass.py deleted file mode 100755 index dc4b1e5..0000000 --- a/contrib/keepassx2pass.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2012 Juhamatti Niemelä . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -import sys -import re - -from subprocess import Popen, PIPE -from xml.etree import ElementTree - -def space_to_camelcase(value): - output = "" - first_word_passed = False - for word in value.split(" "): - if not word: - output += "_" - continue - if first_word_passed: - output += word.capitalize() - else: - output += word.lower() - first_word_passed = True - return output - -def cleanTitle(title): - # make the title more command line friendly - title = re.sub("(\\|\||\(|\))", "-", title) - title = re.sub("-$", "", title) - title = re.sub("\@", "At", title) - title = re.sub("'", "", title) - return title - -def path_for(element, path=''): - """ Generate path name from elements title and current path """ - title = cleanTitle(space_to_camelcase(element.find('title').text)) - return '/'.join([path, title]) - -def password_data(element): - """ Return password data and additional info if available from - password entry element. """ - passwd = element.find('password').text - ret = passwd + "\n" if passwd else "\n" - for field in ['username', 'url', 'comment']: - fel = element.find(field) - if fel.text is not None: - ret = "%s%s: %s\n" % (ret, fel.tag, fel.text) - return ret - -def import_entry(element, path=''): - """ Import new password entry to password-store using pass insert - command """ - proc = Popen(['pass', 'insert', '--multiline', '--force', - path_for(element, path)], - stdin=PIPE, stdout=PIPE) - proc.communicate(password_data(element).encode('utf8')) - proc.wait() - -def import_group(element, path=''): - """ Import all entries and sub-groups from given group """ - npath = path_for(element, path) - for group in element.findall('group'): - import_group(group, npath) - for entry in element.findall('entry'): - import_entry(entry, npath) - - -def main(xml_file): - """ Parse given KeepassX XML file and import password groups from it """ - with open(xml_file) as xml: - for group in ElementTree.XML(xml.read()).findall('group'): - import_group(group) - -if __name__ == '__main__': - main(sys.argv[1]) diff --git a/contrib/lastpass2pass.rb b/contrib/lastpass2pass.rb deleted file mode 100755 index 41a2a29..0000000 --- a/contrib/lastpass2pass.rb +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env ruby - -# Copyright (C) 2012 Alex Sayers . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -# LastPass Importer -# -# Reads CSV files exported from LastPass and imports them into pass. - -# Usage: -# -# Go to lastpass.com and sign in. Next click on your username in the top-right -# corner. In the drop-down meny that appears, click "Export". After filling in -# your details again, copy the text and save it somewhere on your disk. Make sure -# you copy the whole thing, and resist the temptation to "Save Page As" - the -# script doesn't like HTML. -# -# Fire up a terminal and run the script, passing the file you saved as an argument. -# It should look something like this: -# -#$ ./lastpass2pass.rb path/to/passwords_file.csv - -# Parse flags -require 'optparse' -optparse = OptionParser.new do |opts| - opts.banner = "Usage: #{$0} [options] filename" - - FORCE = false - opts.on("-f", "--force", "Overwrite existing records") { FORCE = true } - DEFAULT_GROUP = "" - opts.on("-d", "--default GROUP", "Place uncategorised records into GROUP") { |group| DEFAULT_GROUP = group } - opts.on("-h", "--help", "Display this screen") { puts opts; exit } - - opts.parse! -end - -# Check for a filename -if ARGV.empty? - puts optparse - exit 0 -end - -# Get filename of csv file -filename = ARGV.join(" ") -puts "Reading '#{filename}'..." - - -class Record - def initialize name, url, username, password, extra, grouping, fav - @name, @url, @username, @password, @extra, @grouping, @fav = name, url, username, password, extra, grouping, fav - end - - def name - s = "" - s << @grouping + "/" unless @grouping.empty? - s << @name - s.gsub(/ /, "_").gsub(/'/, "") - end - - def to_s - s = "" - s << "#{@password}\n---\n" - s << "#{@grouping} / " unless @grouping.empty? - s << "#{@name}\n" - s << "username: #{@username}\n" unless @username.empty? - s << "password: #{@password}\n" unless @password.empty? - s << "url: #{@url}\n" unless @url == "http://sn" - s << "#{@extra}\n" unless @extra.nil? - return s - end -end - -# Extract individual records -entries = [] -entry = "" -begin - file = File.open(filename) - file.each do |line| - if line =~ /^http/ - entries.push(entry) - entry = "" - end - entry += line - end - entries.push(entry) - entries.shift - puts "#{entries.length} records found!" -rescue - puts "Couldn't find #{filename}!" - exit 1 -end - -# Parse records and create Record objects -records = [] -entries.each do |e| - args = e.split(",") - url = args.shift - username = args.shift - password = args.shift - fav = args.pop - grouping = args.pop - grouping = DEFAULT_GROUP if grouping.empty? - name = args.pop - extra = args.join(",")[1...-1] - - records << Record.new(name, url, username, password, extra, grouping, fav) -end -puts "Records parsed: #{records.length}" - -successful = 0 -errors = [] -records.each do |r| - print "Creating record #{r.name}..." - IO.popen("pass insert -m#{"f" if FORCE} '#{r.name}' > /dev/null", 'w') do |io| - io.puts r - end - if $? == 0 - puts " done!" - successful += 1 - else - puts " error!" - errors << r - end -end -puts "#{successful} records successfully imported!" - -if errors - puts "There were #{errors.length} errors:" - errors.each { |e| print e.name + (e == errors.last ? ".\n" : ", ")} - puts "These probably occurred because an identically-named record already existed, or because there were multiple entries with the same name in the csv file." -end diff --git a/contrib/pass.bash-completion b/contrib/pass.bash-completion deleted file mode 100644 index d0ef012..0000000 --- a/contrib/pass.bash-completion +++ /dev/null @@ -1,87 +0,0 @@ -# completion file for bash - -# Copyright (C) 2012 Jason A. Donenfeld and -# Brian Mattern . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -_pass_complete_entries () { - prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store/}" - suffix=".gpg" - autoexpand=${1:-0} - - local IFS=$'\n' - local items=($(compgen -f $prefix$cur)) - for item in ${items[@]}; do - if [[ $item == $prefix.* ]]; then - continue - fi - - # if there is a unique match, and it is a directory with one entry - # autocomplete the subentry as well (recursively) - if [[ ${#items[@]} -eq 1 && $autoexpand -eq 1 ]]; then - while [[ -d $item ]]; do - local subitems=($(compgen -f "$item/")) - if [[ ${#subitems[@]} -eq 1 ]]; then - item="${subitems[0]}" - else - break - fi - done - fi - - # append / to directories - [[ -d $item ]] && item="$item/" - - item="${item%$suffix}" - COMPREPLY+=("${item#$prefix}") - done -} - -_pass_complete_keys () { - local IFS=$'\n' - # Extract names and email addresses from gpg --list-keys - local keys="$(gpg2 --list-secret-keys --with-colons | cut -d : -f 10 | sort -u | sed '/^$/d')" - COMPREPLY+=($(compgen -W "${keys}" -- ${cur})) -} - -_pass() -{ - COMPREPLY=() - local cur="${COMP_WORDS[COMP_CWORD]}" - local commands="init ls show insert generate edit rm git help version" - if [[ $COMP_CWORD -gt 1 ]]; then - case "${COMP_WORDS[1]}" in - init) - COMPREPLY+=($(compgen -W "-e --reencrypt" -- ${cur})) - _pass_complete_keys - ;; - ls|list|edit) - _pass_complete_entries - ;; - show|-*) - COMPREPLY+=($(compgen -W "-c --clip" -- ${cur})) - _pass_complete_entries 1 - ;; - insert) - COMPREPLY+=($(compgen -W "-e --echo -m --multiline -f --force" -- ${cur})) - _pass_complete_entries - ;; - generate) - COMPREPLY+=($(compgen -W "-n --no-symbols -c --clip -f --force" -- ${cur})) - _pass_complete_entries - ;; - rm|remove|delete) - COMPREPLY+=($(compgen -W "-r --recursive -f --force" -- ${cur})) - _pass_complete_entries - ;; - git) - COMPREPLY+=($(compgen -W "init push pull config log reflog" -- ${cur})) - ;; - esac - else - COMPREPLY+=($(compgen -W "${commands}" -- ${cur})) - _pass_complete_entries 1 - fi -} - -complete -o filenames -o nospace -F _pass pass diff --git a/contrib/pass.fish-completion b/contrib/pass.fish-completion deleted file mode 100644 index 9130d1f..0000000 --- a/contrib/pass.fish-completion +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env fish - -# Copyright (C) 2012 Dmitry Medvinsky . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -set PROG 'pass' - -function __fish_pass_get_prefix - set -l prefix "$PASSWORD_STORE_DIR" - if [ -z "$prefix" ] - set prefix "$HOME/.password-store" - end - echo "$prefix" -end - -function __fish_pass_needs_command - set -l cmd (commandline -opc) - if [ (count $cmd) -eq 1 -a $cmd[1] = $PROG ] - return 0 - end - return 1 -end -function __fish_pass_uses_command - set cmd (commandline -opc) - if [ (count $cmd) -gt 1 ] - if [ $argv[1] = $cmd[2] ] - return 0 - end - end - return 1 -end - -function __fish_pass_print_gpg_keys - gpg2 --list-keys | grep uid | sed 's/.*<\(.*\)>/\1/' -end -function __fish_pass_print_entry_dirs - set -l prefix (__fish_pass_get_prefix) - set -l dirs - eval "set dirs "$prefix"/**/" - for dir in $dirs - set entry (echo "$dir" | sed "s#$prefix/\(.*\)#\1#") - echo "$entry" - end -end -function __fish_pass_print_entries - set -l prefix (__fish_pass_get_prefix) - set -l files - eval "set files "$prefix"/**.gpg" - for file in $files - set file (echo "$file" | sed "s#$prefix/\(.*\)\.gpg#\1#") - echo "$file" - end -end -function __fish_pass_print_entries_and_dirs - __fish_pass_print_entry_dirs - __fish_pass_print_entries -end - - -complete -c $PROG -e -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a help -d 'Command: show usage help' -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a version -d 'Command: show program version' - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a init -d 'Command: initialize new password storage' -complete -c $PROG -f -A -n '__fish_pass_uses_command init' -s e -l reencrypt -d 'Reencrypt existing passwords using new gpg-id' -complete -c $PROG -f -A -n '__fish_contains_opt -s e reencrypt' -a '(__fish_pass_print_gpg_keys)' - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a ls -d 'Command: list passwords' -complete -c $PROG -f -A -n '__fish_pass_uses_command ls' -a "(__fish_pass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a insert -d 'Command: insert new password' -complete -c $PROG -f -A -n '__fish_pass_uses_command insert' -s e -l echo -d 'Echo the password on console' -complete -c $PROG -f -A -n '__fish_pass_uses_command insert' -s m -l multiline -d 'Provide multiline password entry' -complete -c $PROG -f -A -n '__fish_pass_uses_command insert' -s f -l force -d 'Do not prompt before overwritting' -complete -c $PROG -f -A -n '__fish_pass_uses_command insert' -a "(__fish_pass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a generate -d 'Command: generate new password' -complete -c $PROG -f -A -n '__fish_pass_uses_command generate' -s n -l no-symbols -d 'Do not use special symbols' -complete -c $PROG -f -A -n '__fish_pass_uses_command generate' -s c -l clip -d 'Put the password in clipboard' -complete -c $PROG -f -A -n '__fish_pass_uses_command generate' -s f -l force -d 'Do not prompt before overwritting' -complete -c $PROG -f -A -n '__fish_pass_uses_command generate' -a "(__fish_pass_print_entry_dirs)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a rm -d 'Command: remove existing password' -complete -c $PROG -f -A -n '__fish_pass_uses_command rm' -s r -l recursive -d 'Remove password groups recursively' -complete -c $PROG -f -A -n '__fish_pass_uses_command rm' -s f -l force -d 'Force removal' -complete -c $PROG -f -A -n '__fish_pass_uses_command rm' -a "(__fish_pass_print_entries_and_dirs)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a edit -d 'Command: edit password using text editor' -complete -c $PROG -f -A -n '__fish_pass_uses_command edit' -a "(__fish_pass_print_entries)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a show -d 'Command: show existing password' -complete -c $PROG -f -A -n '__fish_pass_uses_command show' -s c -l clip -d 'Put password in clipboard' -complete -c $PROG -f -A -n '__fish_pass_uses_command show' -a "(__fish_pass_print_entries)" -# When no command is given, `show` is defaulted. -complete -c $PROG -f -A -n '__fish_pass_needs_command' -s c -l clip -d 'Put password in clipboard' -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a "(__fish_pass_print_entries)" -complete -c $PROG -f -A -n '__fish_pass_uses_command -c' -a "(__fish_pass_print_entries)" -complete -c $PROG -f -A -n '__fish_pass_uses_command --clip' -a "(__fish_pass_print_entries)" - -complete -c $PROG -f -A -n '__fish_pass_needs_command' -a git -d 'Command: execute a git command' -complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'init' -d 'Initialize git repository' -complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'push' -d 'Push changes to remote repo' -complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'pull' -d 'Pull changes from remote repo' -complete -c $PROG -f -A -n '__fish_pass_uses_command git' -a 'log' -d 'View changelog' diff --git a/contrib/pass.zsh-completion b/contrib/pass.zsh-completion deleted file mode 100644 index 0bb14de..0000000 --- a/contrib/pass.zsh-completion +++ /dev/null @@ -1,116 +0,0 @@ -#compdef pass - -# Copyright (C) 2012: -# Johan Venant -# Brian Mattern -# Jason A. Donenfeld . -# All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -_pass () { - local cmd - if (( CURRENT > 2)); then - cmd=${words[2]} - # Set the context for the subcommand. - curcontext="${curcontext%:*:*}:pass-$cmd" - # Narrow the range of words we are looking at to exclude `pass' - (( CURRENT-- )) - shift words - # Run the completion for the subcommand - case "${cmd}" in - init) - _arguments : \ - "-r[re-encrypt existing passwords]" \ - "--reencrypt[re-encrypt existing passwords]" - _pass_complete_keys - ;; - ls|list|edit) - _pass_complete_entries_with_subdirs - ;; - insert) - _arguments : \ - "-e[echo password to console]" \ - "--echo[echo password to console]" \ - "-m[multiline]" \ - "--multiline[multiline]" - _pass_complete_entries_with_subdirs - ;; - generate) - _arguments : \ - "-n[don't include symbols in password]" \ - "--no-symbols[don't include symbols in password]" \ - "-c[copy password to the clipboard]" \ - "--clip[copy password to the clipboard]" - _pass_complete_entries_with_subdirs - ;; - rm) - _arguments : \ - "-f[force deletion]" \ - "--force[force deletion]" \ - "-r[recursively delete]" \ - "--recursive[recursively delete]" - _pass_complete_entries_with_subdirs - ;; - git) - local -a subcommands - subcommands=( - "init:Initialize git repository" - "push:Push to remote repository" - "pull:Pull from remote repository" - "config:Show git config" - "log:Show git log" - "reflog:Show git reflog" - ) - _describe -t commands 'pass git' subcommands - ;; - show|*) - _pass_cmd_show - ;; - esac - else - local -a subcommands - subcommands=( - "init:Initialize new password storage" - "ls:List passwords" - "show:Decrypt and print a password" - "insert:Insert a new password" - "generate:Generate a new password using pwgen" - "edit:Edit a password with \$EDITOR" - "rm:Remove the password" - "git:Call git on the password store" - "version:Output version information" - "help:Output help message" - ) - _describe -t commands 'pass' subcommands - _arguments : \ - "--version[Output version information]" \ - "--help[Output help message]" - _pass_cmd_show - fi -} - -_pass_cmd_show () { - _arguments : \ - "-c[put it on the clipboard]" \ - "--clip[put it on the clipboard]" - _pass_complete_entries -} -_pass_complete_entries_helper () { - local IFS=$'\n' - local prefix="${PASSWORD_STORE_DIR:-$HOME/.password-store}" - _values -C 'passwords' $(find -L "$prefix" \( -name .git -o -name .gpg-id \) -prune -o $@ -print | sed -e "s#${prefix}.##" -e 's#\.gpg##' | sort) -} - -_pass_complete_entries_with_subdirs () { - _pass_complete_entries_helper -} - -_pass_complete_entries () { - _pass_complete_entries_helper -type f -} - -_pass_complete_keys () { - local IFS=$'\n' - # Extract names and email addresses from gpg --list-keys - _values 'gpg keys' $(gpg2 --list-secret-keys --with-colons | cut -d : -f 10 | sort -u | sed '/^$/d') -} diff --git a/contrib/pwsafe2pass.sh b/contrib/pwsafe2pass.sh deleted file mode 100755 index c29bb3f..0000000 --- a/contrib/pwsafe2pass.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# Copyright (C) 2013 Tom Hendrikx . All Rights Reserved. -# This file is licensed under the GPLv2+. Please see COPYING for more information. - -export=$1 - -IFS=" " # tab character -cat "$export" | while read uuid group name login passwd notes; do - test "$uuid" = "# passwordsafe version 2.0 database" && continue - test "$uuid" = "uuid" && continue - test "$name" = '""' && continue; - - group="$(echo $group | cut -d'"' -f2)" - login="$(echo $login | cut -d'"' -f2)" - passwd="$(echo $passwd | cut -d'"' -f2)" - name="$(echo $name | cut -d'"' -f2)" - - # cleanup - test "${name:0:4}" = "http" && name="$(echo $name | cut -d'/' -f3)" - test "${name:0:4}" = "www." && name="$(echo $name | cut -c 5-)" - - entry="" - test -n "$login" && entry="${entry}login: $login\n" - test -n "$passwd" && entry="${entry}pass: $passwd\n" - test -n "$group" && entry="${entry}group: $group\n" - - echo Adding entry for $name: - echo -e $entry | pass insert --multiline --force "$name" - test $? && echo "Added!" -done diff --git a/contrib/revelation2pass.py b/contrib/revelation2pass.py deleted file mode 100755 index f04c1a8..0000000 --- a/contrib/revelation2pass.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2013 Emanuele Aina . All Rights Reserved. -# Copyright (C) 2011 Toni Corvera. All Rights Reserved. -# This file is licensed under the BSD 2-clause license: -# http://www.opensource.org/licenses/BSD-2-Clause -# -# Import script for the Revelation password manager: -# http://revelation.olasagasti.info/ -# Heavily based on the Relevation command line tool: -# http://p.outlyer.net/relevation/ - -import os, sys, argparse, zlib, getpass, traceback -from subprocess import Popen, PIPE, STDOUT, CalledProcessError -from collections import OrderedDict -try: - from lxml import etree -except ImportError: - from xml.etree import ElementTree as etree - -USE_PYCRYPTO = True -try: - from Crypto.Cipher import AES -except ImportError: - USE_PYCRYPTO = False - try: - from crypto.cipher import rijndael, cbc - from crypto.cipher.base import noPadding - except ImportError: - sys.stderr.write('Either PyCrypto or cryptopy are required\n') - raise - -def path_for(element, path=None): - """ Generate path name from elements name and current path """ - name = element.find('name').text - name = name.replace('/', '-').replace('\\', '-') - path = path if path else '' - return os.path.join(path, name) - -def format_password_data(data): - """ Format the secret data that will be handed to Pass in multi-line mode: - $password - $fieldname: $fielddata - ... - $multi_line_notes_with_leading_spaces""" - password = data.pop('password', None) or '' - ret = password + '\n' - notes = data.pop('notes', None) - for label, text in data.iteritems(): - ret += label + ': ' + text + '\n' - if notes: - ret += ' ' + notes.replace('\n', '\n ').strip() + '\n' - return ret - -def password_data(element): - """ Return password data and additional info if available from - password entry element. """ - data = OrderedDict() - data['password'] = element.find('field[@id="generic-password"]').text - data['type'] = element.attrib['type'] - for field in element.findall('field'): - field_id = field.attrib['id'] - if field_id == 'generic-password': - continue - if field.text is not None: - data[field_id] = field.text - for tag in ('description', 'notes'): - field = element.find(tag) - if field is not None and field.text: - data[tag] = field.text - return format_password_data(data) - - -def import_entry(element, path=None, verbose=0): - """ Import new password entry to password-store using pass insert - command """ - cmd = ['pass', 'insert', '--multiline', '--force', path_for(element, path)] - if verbose: - print 'cmd:\n ' + ' '.join(cmd) - proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT) - stdin = password_data(element).encode('utf8') - if verbose: - print 'input:\n ' + stdin.replace('\n', '\n ').strip() - stdout, _ = proc.communicate(stdin) - retcode = proc.poll() - if retcode: - raise CalledProcessError(retcode, cmd, output=stdout) - -def import_folder(element, path=None, verbose=0): - path = path_for(element, path) - import_subentries(element, path, verbose) - -def import_subentries(element, path=None, verbose=0): - """ Import all sub entries of the current folder element """ - for entry in element.findall('entry'): - if entry.attrib['type'] == 'folder': - import_folder(entry, path, verbose) - else: - import_entry(entry, path, verbose) - -def decrypt_gz(key, cipher_text): - ''' Decrypt cipher_text using key. - decrypt(str, str) -> cleartext (gzipped xml) - - This function will use the underlying, available, cipher module. - ''' - if USE_PYCRYPTO: - # Extract IV - c = AES.new(key) - iv = c.decrypt(cipher_text[12:28]) - # Decrypt data, CBC mode - c = AES.new(key, AES.MODE_CBC, iv) - ct = c.decrypt(cipher_text[28:]) - else: - # Extract IV - c = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) - iv = c.decrypt(cipher_text[12:28]) - # Decrypt data, CBC mode - bc = rijndael.Rijndael(key, keySize=len(key), padding=noPadding()) - c = cbc.CBC(bc, padding=noPadding()) - ct = c.decrypt(cipher_text[28:], iv=iv) - return ct - -def main(datafile, verbose=False): - f = None - with open(datafile, "rb") as f: - # Encrypted data - data = f.read() - password = getpass.getpass() - # Pad password - password += (chr(0) * (32 - len(password))) - # Decrypt. Decrypted data is compressed - cleardata_gz = decrypt_gz(password, data) - # Length of data padding - padlen = ord(cleardata_gz[-1]) - # Decompress actual data (15 is wbits [ref3] DON'T CHANGE, 2**15 is the (initial) buf size) - xmldata = zlib.decompress(cleardata_gz[:-padlen], 15, 2**15) - root = etree.fromstring(xmldata) - import_subentries(root, verbose=verbose) - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--verbose', '-v', action='count') - parser.add_argument('FILE', help="the file storing the Revelation passwords") - args = parser.parse_args() - - def err(s): - sys.stderr.write(s+'\n') - - try: - main(args.FILE, verbose=args.verbose) - except KeyboardInterrupt: - if args.verbose: - traceback.print_exc() - err(str(e)) - except zlib.error: - err('Failed to decompress decrypted data. Wrong password?') - sys.exit(os.EX_DATAERR) - except CalledProcessError as e: - if args.verbose: - traceback.print_exc() - print 'output:\n ' + e.output.replace('\n', '\n ').strip() - else: - err('CalledProcessError: ' + str(e)) - sys.exit(os.EX_IOERR) - except IOError as e: - if args.verbose: - traceback.print_exc() - else: - err('IOError: ' + str(e)) - sys.exit(os.EX_IOERR) -- cgit v1.2.3-59-g8ed1b