password-store

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

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