rubeepass 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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