rubeepass 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8635dc28cb56a0bc98a6c2d8ac0f0029a1d16005
4
+ data.tar.gz: 83416771d1bf5a6ebd2d66cde4603b9de3d55cd5
5
+ SHA512:
6
+ metadata.gz: ee4d0478d32fa683fbde3a7e80f9c42b48ce4e4248285e7ef7b094ee66c60918fd1c1563403fa7d59b6682a7a4670055853cbb8bae0fa7e66f453e5bce5d68a8
7
+ data.tar.gz: ee51c7575168d1727527690cb6d064f2d21fc2f6ff8ee877360744ec71590569dd14320ffb8467e6446e98878af97eb6bc2e8328da81a6e3e067b73311c7e5e1
data/bin/rpass ADDED
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "djinni"
4
+ require "io/console"
5
+ require "json"
6
+ require "optparse"
7
+ require "pathname"
8
+ require "rubeepass"
9
+ require "string"
10
+
11
+ class RPassExit
12
+ GOOD = 0
13
+ INVALID_OPTION = 1
14
+ INVALID_ARGUMENT = 2
15
+ MISSING_ARGUMENT = 3
16
+ EXTRA_ARGUMENTS = 4
17
+ KDBX_NOT_FOUND = 5
18
+ KDBX_NOT_READABLE = 6
19
+ KEYFILE_NOT_FOUND = 7
20
+ KEYFILE_NOT_READABLE = 8
21
+ KDBX_NOT_OPENED = 9
22
+ end
23
+
24
+ def get_password
25
+ print "Enter password: "
26
+ passwd = STDIN.noecho(&:gets)
27
+ puts
28
+ return passwd.chomp
29
+ end
30
+
31
+ def parse(args)
32
+ options = Hash.new
33
+ options["export_file"] = nil
34
+ options["export_format"] = "xml"
35
+ options["password"] = nil
36
+ options["keyfile"] = nil
37
+ options["timeout"] = nil
38
+
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: #{File.basename($0)} [OPTIONS] [kdbx]"
41
+
42
+ opts.on(
43
+ "-e",
44
+ "--export=FILE",
45
+ "Export database to file"
46
+ ) do |file|
47
+ options["export_file"] = file
48
+ end
49
+
50
+ opts.on(
51
+ "-f",
52
+ "--format=FORMAT",
53
+ [ "gzip", "xml" ],
54
+ "Specify format to use when exporting (default: xml)"
55
+ ) do |format|
56
+ options["export_format"] = format
57
+ end
58
+
59
+ opts.on("-h", "--help", "Display this help message") do
60
+ puts opts
61
+ exit RPassExit::GOOD
62
+ end
63
+
64
+ opts.on(
65
+ "-k",
66
+ "--keyfile=KEYFILE",
67
+ "Use specified keyfile"
68
+ ) do |keyfile|
69
+ options["keyfile"] = Pathname.new(keyfile).expand_path
70
+ end
71
+
72
+ opts.on(
73
+ "-p",
74
+ "--password=PASSWORD",
75
+ "Use specified password (will prompt if not provided)"
76
+ ) do |password|
77
+ options["password"] = password
78
+ end
79
+
80
+ opts.on("-t", "--timeout=TIMEOUT", "Clipboard timeout") do |t|
81
+ options["timeout"] = t.to_i
82
+ end
83
+
84
+ opts.on(
85
+ "",
86
+ "FORMATS",
87
+ "\tgzip",
88
+ "\txml"
89
+ )
90
+ end
91
+
92
+ begin
93
+ parser.parse!
94
+ rescue OptionParser::InvalidOption => e
95
+ puts e.message
96
+ puts parser
97
+ exit RPassExit::INVALID_OPTION
98
+ rescue OptionParser::InvalidArgument => e
99
+ puts e.message
100
+ puts parser
101
+ exit RPassExit::INVALID_ARGUMENT
102
+ rescue OptionParser::MissingArgument => e
103
+ puts e.message
104
+ puts parser
105
+ exit RPassExit::MISSING_ARGUMENT
106
+ end
107
+
108
+ if (args.length > 1)
109
+ puts parser
110
+ exit RPassExit::EXTRA_ARGUMENTS
111
+ end
112
+
113
+ # Read config
114
+ rc = read_rpassrc
115
+
116
+ # Determine kdbx and keyfile
117
+ if (args.length == 1)
118
+ # Use specified kdbx (and keyfile if specified)
119
+ options["kdbx"] = Pathname.new(args[0]).expand_path
120
+ else
121
+ # Use kdbx from config if stored
122
+ if (rc["last_kdbx"] && !rc["last_kdbx"].empty?)
123
+ options["kdbx"] = Pathname.new(
124
+ rc["last_kdbx"]
125
+ ).expand_path
126
+ end
127
+
128
+ # Use keyfile from config if stored and not specified already
129
+ if (options["keyfile"].nil?)
130
+ if (rc["last_keyfile"] && !rc["last_keyfile"].empty?)
131
+ options["keyfile"] = Pathname.new(
132
+ rc["last_keyfile"]
133
+ ).expand_path
134
+ end
135
+ end
136
+ end
137
+
138
+ # Determine timeout
139
+ if (options["timeout"].nil?)
140
+ if (rc["timeout"])
141
+ options["timeout"] = rc["timeout"]
142
+ else
143
+ options["timeout"] = 7
144
+ end
145
+ end
146
+
147
+ # Throw error if kdbx not specified or in config
148
+ if (options["kdbx"].nil?)
149
+ puts parser
150
+ exit RPassExit::MISSING_ARGUMENT
151
+ end
152
+
153
+ # Throw error if kdbx does not exist or is not readable
154
+ if (!options["kdbx"].exist?)
155
+ puts parser
156
+ exit RPassExit::KDBX_NOT_FOUND
157
+ elsif (!options["kdbx"].readable?)
158
+ puts parser
159
+ exit RPassExit::KDBX_NOT_READABLE
160
+ end
161
+
162
+ # Throw error if keyfile does not exist or is not readable
163
+ if (options["keyfile"])
164
+ if (!options["keyfile"].exist?)
165
+ puts parser
166
+ exit RPassExit::KEYFILE_NOT_FOUND
167
+ elsif (!options["keyfile"].readable?)
168
+ puts parser
169
+ exit RPassExit::KEYFILE_NOT_READABLE
170
+ end
171
+ end
172
+
173
+ # Store data in config
174
+ rc["last_kdbx"] = options["kdbx"]
175
+ rc["last_keyfile"] = options["keyfile"]
176
+ rc["timeout"] = options["timeout"]
177
+ write_rpassrc(rc)
178
+
179
+ return options
180
+ end
181
+
182
+ def read_rpassrc
183
+ default = Hash.new
184
+ default["last_kdbx"] = nil
185
+ default["last_keyfile"] = nil
186
+ default["timeout"] = 7
187
+
188
+ rc_file = Pathname.new("~/.rpassrc").expand_path
189
+ return default if (!rc_file.exist? && !rc_file.symlink?)
190
+ return JSON.parse(File.read(rc_file))
191
+ end
192
+
193
+ def write_rpassrc(rc)
194
+ rc_file = Pathname.new("~/.rpassrc").expand_path
195
+ File.open(rc_file, "w") do |f|
196
+ f.write(JSON.pretty_generate(rc))
197
+ end
198
+ end
199
+
200
+ options = parse(ARGV)
201
+
202
+ kdbx = options["kdbx"]
203
+ password = options["password"] if (options["password"])
204
+ password = get_password if (options["password"].nil?)
205
+ keyfile = options["keyfile"]
206
+
207
+ keepass = nil
208
+ begin
209
+ keepass = RubeePass.new(kdbx, password, keyfile).open
210
+ rescue RubeePass::Error => e
211
+ puts e.message
212
+ exit RPassExit::KDBX_NOT_OPENED
213
+ end
214
+ exit RPassExit::KDBX_NOT_OPENED if (keepass.nil?)
215
+
216
+ if (options["export_file"])
217
+ File.open(options["export_file"], "w") do |f|
218
+ case options["export_format"]
219
+ when "gzip"
220
+ f.write(keepass.gzip)
221
+ when "xml"
222
+ f.write(keepass.xml)
223
+ end
224
+ end
225
+ exit RPassExit::GOOD
226
+ end
227
+
228
+ djinni = Djinni.new
229
+ djinni.load_wishes("#{File.dirname(__FILE__)}/../lib/builtins")
230
+ djinni.prompt(
231
+ {
232
+ "keepass" => keepass,
233
+ "cwd" => keepass.db,
234
+ "clipboard_timeout" => options["timeout"]
235
+ },
236
+ "rpass:/> ".white
237
+ )
238
+ exit RPassExit::GOOD
@@ -0,0 +1,56 @@
1
+ require "djinni"
2
+ require "string"
3
+
4
+ class CDWish < Djinni::Wish
5
+ def aliases
6
+ return [ "cd" ]
7
+ end
8
+
9
+ def description
10
+ return "Change to new group"
11
+ end
12
+
13
+ def execute(args, djinni_env = {})
14
+ keepass = djinni_env["keepass"]
15
+ cwd = djinni_env["cwd"]
16
+
17
+ args = keepass.absolute_path(args, cwd.path)
18
+ new_cwd = keepass.find_group(args)
19
+
20
+ if (new_cwd)
21
+ djinni_env["cwd"] = new_cwd
22
+ prompt = "rpass:#{new_cwd.name}> ".white
23
+ djinni_env["djinni_prompt"] = prompt
24
+ else
25
+ puts "Group \"#{args}\" doesn't exist!"
26
+ end
27
+ end
28
+
29
+ def tab_complete(input, djinni_env = {})
30
+ cwd = djinni_env["cwd"]
31
+ input, groups = cwd.fuzzy_find(input)
32
+ return input.gsub(%r{^#{cwd.path}/?}, "") if (groups.empty?)
33
+
34
+ path, dest = input.rsplit("/")
35
+
36
+ if (dest.empty?)
37
+ if (groups.length == 1)
38
+ input = "#{path}/#{groups.first}/"
39
+ return input.gsub(%r{^#{cwd.path}/?}, "")
40
+ end
41
+ puts
42
+ groups.each do |group|
43
+ puts "#{group}/"
44
+ end
45
+ return input.gsub(%r{^#{cwd.path}/?}, "")
46
+ end
47
+
48
+ input = "#{path}/#{groups.first}/"
49
+ return input.gsub(%r{^#{cwd.path}/?}, "")
50
+ end
51
+
52
+ def usage
53
+ puts "#{aliases.join(", ")} [group]"
54
+ puts "\t#{description}."
55
+ end
56
+ end
@@ -0,0 +1,24 @@
1
+ require "djinni"
2
+
3
+ class ClearWish < Djinni::Wish
4
+ def aliases
5
+ return [ "clear", "cls" ]
6
+ end
7
+
8
+ def description
9
+ return "Clear the screen"
10
+ end
11
+
12
+ def execute(args, djinni_env = {})
13
+ if (args.nil? || args.empty?)
14
+ system("clear")
15
+ else
16
+ usage
17
+ end
18
+ end
19
+
20
+ def usage
21
+ puts aliases.join(", ")
22
+ puts "\t#{description}."
23
+ end
24
+ end
@@ -0,0 +1,134 @@
1
+ require "djinni"
2
+ require "string"
3
+
4
+ class CopyWish < Djinni::Wish
5
+ def aliases
6
+ return [ "copy", "cp" ]
7
+ end
8
+
9
+ def description
10
+ return "Copy specified field to the clipboard"
11
+ end
12
+
13
+ def execute(args, djinni_env = {})
14
+ if (args.nil? || args.empty?)
15
+ puts usage
16
+ return
17
+ end
18
+
19
+ field, args = args.split(" ", 2)
20
+ if (!@fields.include?(field))
21
+ puts usage
22
+ return
23
+ end
24
+
25
+ keepass = djinni_env["keepass"]
26
+ cwd = djinni_env["cwd"]
27
+ args = cwd.path if (args.nil? || args.empty?)
28
+
29
+ args = keepass.absolute_path(args, cwd.path)
30
+ path, target = args.rsplit("/")
31
+ new_cwd = keepass.find_group(path)
32
+
33
+ if (new_cwd)
34
+ if (target.empty?)
35
+ usage
36
+ elsif (new_cwd.has_entry?(target))
37
+ target = new_cwd.entry_titles.select do |entry|
38
+ target.downcase == entry.downcase
39
+ end.first
40
+
41
+ timeout = djinni_env["clipboard_timeout"]
42
+
43
+ case field
44
+ when "pass"
45
+ new_cwd.entries[target].copy_password_to_clipboard
46
+ keepass.send(
47
+ "clear_clipboard_after_#{timeout}_seconds"
48
+ )
49
+ when "url"
50
+ new_cwd.entries[target].copy_url_to_clipboard
51
+ keepass.send(
52
+ "clear_clipboard_after_#{timeout}_seconds"
53
+ )
54
+ when "user"
55
+ new_cwd.entries[target].copy_username_to_clipboard
56
+ keepass.send(
57
+ "clear_clipboard_after_#{timeout}_seconds"
58
+ )
59
+ end
60
+ else
61
+ puts "Entry \"#{args}\" doesn't exist!"
62
+ end
63
+ else
64
+ puts "Entry \"#{args}\" doesn't exist!"
65
+ end
66
+ end
67
+
68
+ def initialize
69
+ @fields = [ "pass", "url", "user" ]
70
+ end
71
+
72
+ def tab_complete(input, djinni_env = {})
73
+ if (input.nil? || input.empty?)
74
+ puts
75
+ puts @fields
76
+ return ""
77
+ end
78
+
79
+ field, input = input.split(" ", 2)
80
+
81
+ if (input.nil? || input.empty?)
82
+ @fields.each do |f|
83
+ break if (f == field)
84
+ if (f.start_with?(field))
85
+ return "#{f} "
86
+ end
87
+ end
88
+ end
89
+
90
+ input = "" if (input.nil?)
91
+
92
+ cwd = djinni_env["cwd"]
93
+ input, groups, entries = cwd.fuzzy_find(input)
94
+ if (groups.empty? && entries.empty?)
95
+ return "#{field} #{input.gsub(%r{^#{cwd.path}/?}, "")}"
96
+ end
97
+
98
+ path, target = input.rsplit("/")
99
+
100
+ if (target.empty?)
101
+ if ((groups.length == 1) && entries.empty?)
102
+ input = "#{path}/#{groups.first}/"
103
+ return "#{field} #{input.gsub(%r{^#{cwd.path}/?}, "")}"
104
+ elsif (groups.empty? && (entries.length == 1))
105
+ input = "#{path}/#{entries.first}"
106
+ return "#{field} #{input.gsub(%r{^#{cwd.path}/?}, "")}"
107
+ end
108
+ puts
109
+ groups.each do |group|
110
+ puts "#{group}/"
111
+ end
112
+ puts entries
113
+ return "#{field} #{input.gsub(%r{^#{cwd.path}/?}, "")}"
114
+ end
115
+
116
+ if (!groups.empty?)
117
+ input = "#{path}/#{groups.first}/"
118
+ elsif (!entries.empty?)
119
+ input = "#{path}/#{entries.first}"
120
+ end
121
+
122
+ return "#{field} #{input.gsub(%r{^#{cwd.path}/?}, "")}"
123
+ end
124
+
125
+ def usage
126
+ puts "#{aliases.join(", ")} <field> <entry>"
127
+ puts "\t#{description}."
128
+ puts
129
+ puts "FIELDS"
130
+ @fields.each do |field|
131
+ puts "\t#{field}"
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,60 @@
1
+ require "djinni"
2
+ require "string"
3
+
4
+ class LSWish < Djinni::Wish
5
+ def aliases
6
+ return [ "ls", "dir" ]
7
+ end
8
+
9
+ def description
10
+ return "List groups and entries in current group"
11
+ end
12
+
13
+ def execute(args, djinni_env = {})
14
+ keepass = djinni_env["keepass"]
15
+ cwd = djinni_env["cwd"]
16
+ args = cwd.path if (args.nil? || args.empty?)
17
+
18
+ args = keepass.absolute_path(args, cwd.path)
19
+ new_cwd = keepass.find_group(args)
20
+
21
+ if (new_cwd)
22
+ new_cwd.group_names.each do |group|
23
+ puts "#{group}/"
24
+ end
25
+ new_cwd.entry_titles.each do |entry|
26
+ puts "#{entry}"
27
+ end
28
+ else
29
+ puts "Group \"#{args}\" doesn't exist!"
30
+ end
31
+ end
32
+
33
+ def tab_complete(input, djinni_env = {})
34
+ cwd = djinni_env["cwd"]
35
+ input, groups = cwd.fuzzy_find(input)
36
+ return input.gsub(%r{^#{cwd.path}/?}, "") if (groups.empty?)
37
+
38
+ path, dest = input.rsplit("/")
39
+
40
+ if (dest.empty?)
41
+ if (groups.length == 1)
42
+ input = "#{path}/#{groups.first}/"
43
+ return input.gsub(%r{^#{cwd.path}/?}, "")
44
+ end
45
+ puts
46
+ groups.each do |group|
47
+ puts "#{group}/"
48
+ end
49
+ return input.gsub(%r{^#{cwd.path}/?}, "")
50
+ end
51
+
52
+ input = "#{path}/#{groups.first}/"
53
+ return input.gsub(%r{^#{cwd.path}/?}, "")
54
+ end
55
+
56
+ def usage
57
+ puts "#{aliases.join(", ")} [group]"
58
+ puts "\t#{description}."
59
+ end
60
+ end
@@ -0,0 +1,24 @@
1
+ require "djinni"
2
+
3
+ class PwdWish < Djinni::Wish
4
+ def aliases
5
+ return [ "pwd" ]
6
+ end
7
+
8
+ def description
9
+ return "Show path of current group"
10
+ end
11
+
12
+ def execute(args, djinni_env = {})
13
+ if (args.nil? || args.empty?)
14
+ puts djinni_env["cwd"].path
15
+ else
16
+ usage
17
+ end
18
+ end
19
+
20
+ def usage
21
+ puts aliases.join(", ")
22
+ puts "\t#{description}."
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ require "djinni"
2
+ require "string"
3
+
4
+ class ShowWish < Djinni::Wish
5
+ def aliases
6
+ return [ "cat", "show" ]
7
+ end
8
+
9
+ def description
10
+ return "Show group contents"
11
+ end
12
+
13
+ def execute(args, djinni_env = {})
14
+ keepass = djinni_env["keepass"]
15
+ cwd = djinni_env["cwd"]
16
+ args = cwd.path if (args.nil? || args.empty?)
17
+
18
+ args = keepass.absolute_path(args, cwd.path)
19
+ path, target = args.rsplit("/")
20
+ new_cwd = keepass.find_group(path)
21
+
22
+ if (new_cwd)
23
+ if (target.empty?)
24
+ puts new_cwd
25
+ elsif (new_cwd.has_group?(target))
26
+ puts new_cwd.groups[target]
27
+ elsif (new_cwd.has_entry?(target))
28
+ new_cwd.entry_titles.select do |entry|
29
+ target.downcase == entry.downcase
30
+ end.each do |entry|
31
+ puts new_cwd.entries[entry]
32
+ end
33
+ else
34
+ puts "Group/entry \"#{args}\" doesn't exist!"
35
+ end
36
+ else
37
+ puts "Group/entry \"#{args}\" doesn't exist!"
38
+ end
39
+ end
40
+
41
+ def tab_complete(input, djinni_env = {})
42
+ cwd = djinni_env["cwd"]
43
+ input, groups, entries = cwd.fuzzy_find(input)
44
+ if (groups.empty? && entries.empty?)
45
+ return input.gsub(%r{^#{cwd.path}/?}, "")
46
+ end
47
+
48
+ path, target = input.rsplit("/")
49
+
50
+ if (target.empty?)
51
+ if ((groups.length == 1) && entries.empty?)
52
+ input = "#{path}/#{groups.first}/"
53
+ return input.gsub(%r{^#{cwd.path}/?}, "")
54
+ elsif (groups.empty? && (entries.length == 1))
55
+ input = "#{path}/#{entries.first}"
56
+ return input.gsub(%r{^#{cwd.path}/?}, "")
57
+ end
58
+ puts
59
+ groups.each do |group|
60
+ puts "#{group}/"
61
+ end
62
+ puts entries
63
+ return input.gsub(%r{^#{cwd.path}/?}, "")
64
+ end
65
+
66
+ if (!groups.empty?)
67
+ input = "#{path}/#{groups.first}/"
68
+ elsif (!entries.empty?)
69
+ input = "#{path}/#{entries.first}"
70
+ end
71
+
72
+ return input.gsub(%r{^#{cwd.path}/?}, "")
73
+ end
74
+
75
+ def usage
76
+ puts "#{aliases.join(", ")} [group]"
77
+ puts "\t#{description}."
78
+ end
79
+ end
@@ -0,0 +1,74 @@
1
+ require "string"
2
+
3
+ class RubeePass::Entry
4
+ include Comparable
5
+
6
+ attr_accessor :group
7
+ attr_accessor :keepass
8
+ attr_accessor :notes
9
+ attr_accessor :path
10
+ attr_accessor :title
11
+ attr_accessor :url
12
+ attr_accessor :username
13
+ attr_accessor :uuid
14
+
15
+ def ==(other)
16
+ return (self.uuid == other.uuid)
17
+ end
18
+
19
+ def <=>(other)
20
+ return (self.title.downcase <=> other.title.downcase)
21
+ end
22
+
23
+ def details(level = 0)
24
+ lvl = Array.new(level, " ").join
25
+
26
+ return [
27
+ "#{lvl}Title : #{@title}".green,
28
+ # "#{lvl}UUID : #{@uuid}",
29
+ "#{lvl}Username : #{@username}",
30
+ # "#{lvl}Password : #{password}".red,
31
+ "#{lvl}Url : #{@url}",
32
+ "#{lvl}Notes : #{@notes}"
33
+ ].join("\n")
34
+ end
35
+
36
+ def initialize(params)
37
+ @group = params.fetch("Group", nil)
38
+ @keepass = params.fetch("Keepass", nil)
39
+ @password = params.fetch("Notes", "")
40
+ @password = params.fetch("Password", "")
41
+ @title = params.fetch("Title", "")
42
+ @url = params.fetch("URL", "")
43
+ @username = params.fetch("UserName", "")
44
+ @uuid = params.fetch("UUID", "")
45
+
46
+ @path = @title
47
+ @path = "#{@group.path}/#{@title}" if (@group)
48
+ @path.gsub!(%r{^//}, "/")
49
+ end
50
+
51
+ def method_missing(method_name, *args)
52
+ super if (@keepass.nil?)
53
+
54
+ case method_name.to_s.gsub(/^copy_(.+)_to_clipboard$/, "\\1")
55
+ when "password"
56
+ @keepass.copy_to_clipboard(password)
57
+ when "url"
58
+ @keepass.copy_to_clipboard(@url)
59
+ when "username"
60
+ @keepass.copy_to_clipboard(@username)
61
+ else
62
+ super
63
+ end
64
+ end
65
+
66
+ def password
67
+ return nil if (@keepass.nil?)
68
+ return @keepass.protected_decryptor.get_password(@password)
69
+ end
70
+
71
+ def to_s
72
+ return details
73
+ end
74
+ end