From 82992e177c284d51170fad92438e4cf3a416360e Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sun, 7 Oct 2012 13:41:30 +0200 Subject: Add Alex Sayer's lastpass_import.rb script. --- contrib/lastpass2pass.rb | 138 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100755 contrib/lastpass2pass.rb (limited to 'contrib/lastpass2pass.rb') diff --git a/contrib/lastpass2pass.rb b/contrib/lastpass2pass.rb new file mode 100755 index 0000000..e886950 --- /dev/null +++ b/contrib/lastpass2pass.rb @@ -0,0 +1,138 @@ +#!/usr/bin/env ruby + +# Copyright (C) Alex Sayers . All Rights Reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# 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: +# +# ./lastpass_importer.rb path/to/passwords_file + + +# Set this variable to place all uncategorised records into a particular group +DEFAULT_GROUP = "" + +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 + +# Check for a filename +if ARGV.empty? + puts "Usage: lastpass_importer.rb import records from specified file" + exit 0 +end + +# Get filename of csv file +filename = ARGV.join(" ") +puts "Reading '#{filename}'..." + +# 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 '#{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 -- cgit v1.2.3-59-g8ed1b