lastpass2pass.rb (3637B)
1 #!/usr/bin/env ruby 2 3 # Copyright (C) 2012 Alex Sayers <alex.sayers@gmail.com>. All Rights Reserved. 4 # This file is licensed under the GPLv2+. Please see COPYING for more information. 5 6 # LastPass Importer 7 # 8 # Reads CSV files exported from LastPass and imports them into pass. 9 10 # Usage: 11 # 12 # Go to lastpass.com and sign in. Next click on your username in the top-right 13 # corner. In the drop-down meny that appears, click "Export". After filling in 14 # your details again, copy the text and save it somewhere on your disk. Make sure 15 # you copy the whole thing, and resist the temptation to "Save Page As" - the 16 # script doesn't like HTML. 17 # 18 # Fire up a terminal and run the script, passing the file you saved as an argument. 19 # It should look something like this: 20 # 21 #$ ./lastpass2pass.rb path/to/passwords_file.csv 22 23 # Parse flags 24 require 'optparse' 25 optparse = OptionParser.new do |opts| 26 opts.banner = "Usage: #{$0} [options] filename" 27 28 FORCE = false 29 opts.on("-f", "--force", "Overwrite existing records") { FORCE = true } 30 DEFAULT_GROUP = "" 31 opts.on("-d", "--default GROUP", "Place uncategorised records into GROUP") { |group| DEFAULT_GROUP = group } 32 opts.on("-h", "--help", "Display this screen") { puts opts; exit } 33 34 opts.parse! 35 end 36 37 # Check for a filename 38 if ARGV.empty? 39 puts optparse 40 exit 0 41 end 42 43 # Get filename of csv file 44 filename = ARGV.join(" ") 45 puts "Reading '#{filename}'..." 46 47 48 class Record 49 def initialize name, url, username, password, extra, grouping, fav 50 @name, @url, @username, @password, @extra, @grouping, @fav = name, url, username, password, extra, grouping, fav 51 end 52 53 def name 54 s = "" 55 s << @grouping + "/" unless @grouping.empty? 56 s << @name unless @name == nil 57 s.gsub(/ /, "_").gsub(/'/, "") 58 end 59 60 def to_s 61 s = "" 62 s << "#{@password}\n---\n" 63 s << "#{@grouping} / " unless @grouping.empty? 64 s << "#{@name}\n" 65 s << "username: #{@username}\n" unless @username.empty? 66 s << "password: #{@password}\n" unless @password.empty? 67 s << "url: #{@url}\n" unless @url == "http://sn" 68 s << "#{@extra}\n" unless @extra.nil? 69 return s 70 end 71 end 72 73 # Extract individual records 74 entries = [] 75 entry = "" 76 begin 77 file = File.open(filename) 78 file.each do |line| 79 if line =~ /^(http|ftp|ssh)/ 80 entries.push(entry) 81 entry = "" 82 end 83 entry += line 84 end 85 entries.push(entry) 86 entries.shift 87 puts "#{entries.length} records found!" 88 rescue 89 puts "Couldn't find #{filename}!" 90 exit 1 91 end 92 93 # Parse records and create Record objects 94 records = [] 95 entries.each do |e| 96 args = e.split(",") 97 url = args.shift 98 username = args.shift 99 password = args.shift 100 fav = args.pop 101 grouping = args.pop 102 grouping = DEFAULT_GROUP if grouping == nil 103 name = args.pop 104 extra = args.join(",")[1...-1] 105 106 records << Record.new(name, url, username, password, extra, grouping, fav) 107 end 108 puts "Records parsed: #{records.length}" 109 110 successful = 0 111 errors = [] 112 records.each do |r| 113 if File.exist?("#{r.name}.gpg") and FORCE == false 114 puts "skipped #{r.name}: already exists" 115 next 116 end 117 print "Creating record #{r.name}..." 118 IO.popen("pass insert -m '#{r.name}' > /dev/null", 'w') do |io| 119 io.puts r 120 end 121 if $? == 0 122 puts " done!" 123 successful += 1 124 else 125 puts " error!" 126 errors << r 127 end 128 end 129 puts "#{successful} records successfully imported!" 130 131 if errors.length > 0 132 puts "There were #{errors.length} errors:" 133 errors.each { |e| print e.name + (e == errors.last ? ".\n" : ", ")} 134 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." 135 end