gtk2passwordapp 0.0.1
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.
- data/README.txt +8 -0
- data/bin/gtk2passwordapp +41 -0
- data/gifs/logo.gif +0 -0
- data/lib/configuration.rb +49 -0
- data/lib/global_options_variables.rb +40 -0
- data/lib/gtk2passwordapp.rb +597 -0
- data/lib/iocrypt.rb +30 -0
- data/lib/passwords_data.rb +110 -0
- data/lib/setup_user_space.rb +37 -0
- metadata +69 -0
data/README.txt
ADDED
data/bin/gtk2passwordapp
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# $Date: 2009/02/19 15:50:30 $
|
3
|
+
##########################################################
|
4
|
+
require 'lib/global_options_variables'
|
5
|
+
GlobalOptionsVariables.set('0.0.1',
|
6
|
+
<<EOT
|
7
|
+
Usage: #{$0.sub(/^.*\//,'')} [options]
|
8
|
+
|
9
|
+
Options:
|
10
|
+
-h, --help print this help text and exit
|
11
|
+
-v, --version print program version and exit
|
12
|
+
-t, --test test
|
13
|
+
-T, --trace trace
|
14
|
+
EOT
|
15
|
+
)
|
16
|
+
require 'lib/setup_user_space'
|
17
|
+
UserSpace.setup
|
18
|
+
UserSpace.mkdir('/gifs')
|
19
|
+
UserSpace.copy('/gifs/logo.gif')
|
20
|
+
require USER_CONF_DIR+CONF_FILE
|
21
|
+
##########################################################
|
22
|
+
require 'lib/gtk2passwordapp'
|
23
|
+
|
24
|
+
lock = USER_CONF_DIR+'/lock'
|
25
|
+
if File.exist?(lock) then
|
26
|
+
$stderr.puts "process already running?"
|
27
|
+
# user should then notice it's already running, but
|
28
|
+
# let's remove the lock in case it's not and user
|
29
|
+
# tries again....
|
30
|
+
File.unlink(lock)
|
31
|
+
else
|
32
|
+
begin
|
33
|
+
File.open(lock,'w'){|fh| fh.puts $$ }
|
34
|
+
gpa = Gtk2PasswordApp.new
|
35
|
+
gpa.status_icon
|
36
|
+
rescue Exception
|
37
|
+
puts_bang!
|
38
|
+
ensure
|
39
|
+
File.unlink(lock) if File.exist?(lock)
|
40
|
+
end
|
41
|
+
end
|
data/gifs/logo.gif
ADDED
Binary file
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#$Date: 2009/02/19 17:56:37 $
|
2
|
+
# Note: you'll see in ~/.gtk2passwordapp a file called passphrase.txt.
|
3
|
+
# Do not edit or delete passphrase, or you'll loose your passwords data.
|
4
|
+
module Configuration
|
5
|
+
ENTRY_WIDTH = 275
|
6
|
+
LABEL_WIDTH = 75
|
7
|
+
SPIN_BUTTON_LENGTH = 50
|
8
|
+
PAD = 2 # cell padding
|
9
|
+
|
10
|
+
FONT_NAME = 'Arial 10'
|
11
|
+
|
12
|
+
MAX_PASSWORD_LENGTH = 20
|
13
|
+
DEFAULT_PASSWORD_LENGTH = 7
|
14
|
+
MIN_PASSWORD_LENGTH = 3
|
15
|
+
|
16
|
+
VERIFIED_EXPIRED = 60*15 # 15 minutes
|
17
|
+
PASSWORD_EXPIRED = 60*60*24*30*3 # 3 months
|
18
|
+
|
19
|
+
URL_PATTERN = Regexp.new('^https?:\/\/[^\s\']+$')
|
20
|
+
|
21
|
+
# Here you can set your browser preferences for remote control.
|
22
|
+
# As is, the first browser found will be used, like
|
23
|
+
# BROWSER = '/usr/bin/epiphany -n'
|
24
|
+
[
|
25
|
+
# browser url remote option code
|
26
|
+
[ 'browser', '--url' ],
|
27
|
+
[ 'fennec', '' ],
|
28
|
+
[ 'epiphany', '-n' ],
|
29
|
+
[ 'firefox', '-new-tab' ],
|
30
|
+
[ 'opera', '' ]
|
31
|
+
].each {|try|
|
32
|
+
browser = `which #{try[0]} 2> /dev/null`.strip
|
33
|
+
next if browser == ''
|
34
|
+
BROWSER = "#{browser} #{try[1]}"
|
35
|
+
break
|
36
|
+
}
|
37
|
+
$stderr.puts "Browser remote command: #{BROWSER}" if $trace
|
38
|
+
|
39
|
+
LOGO_IMAGE = USER_CONF_DIR+'/gifs/logo.gif'
|
40
|
+
end
|
41
|
+
|
42
|
+
def puts_bang!(h=nil, t=nil)
|
43
|
+
if $trace then
|
44
|
+
$stderr.puts h if h
|
45
|
+
$stderr.puts $!
|
46
|
+
$stderr.puts $!.backtrace
|
47
|
+
$stderr.puts t if t
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# $Date: 2009/02/14 16:30:05 $
|
2
|
+
GEM_LIB_DIR = File.expand_path( File.dirname(__FILE__) )
|
3
|
+
GEM = GEM_LIB_DIR.split(/[\/\\]/)[-2]
|
4
|
+
USER_CONF_DIR = ENV['HOME'] + "/.#{GEM}"
|
5
|
+
GEM_ROOT_DIR = GEM_LIB_DIR.sub(/\/[^\/\\]+$/,'')
|
6
|
+
CONF_FILE = '/configuration.rb'
|
7
|
+
module GlobalOptionsVariables
|
8
|
+
def self.set( version, help=nil, additional_options={} )
|
9
|
+
raise "globals have been set elsewhere :-??" if $version || $quiet || $test || $trace
|
10
|
+
$version = version
|
11
|
+
|
12
|
+
options_map = {
|
13
|
+
'--help' => 'h',
|
14
|
+
'--quiet' => 'q',
|
15
|
+
'--test' => 't',
|
16
|
+
'--trace' => 'T',
|
17
|
+
}
|
18
|
+
|
19
|
+
options = ''
|
20
|
+
opt = nil
|
21
|
+
while ARGV[0]=~/^\-/ do
|
22
|
+
opt = ARGV.shift
|
23
|
+
options += (additional_options[opt])? additional_options[opt]: (options_map[opt])? options_map[opt]: opt
|
24
|
+
end
|
25
|
+
|
26
|
+
if help && options=~/h/ then
|
27
|
+
puts help if help
|
28
|
+
exit
|
29
|
+
elsif options=~/v/ then
|
30
|
+
puts $version
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
$quiet = (options=~/q/)? true: false
|
35
|
+
$test = (options=~/t/)? true: false
|
36
|
+
$trace = (options=~/t/i)? true: false
|
37
|
+
|
38
|
+
return options
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,597 @@
|
|
1
|
+
# $Date: 2009/02/19 20:50:22 $
|
2
|
+
require 'lib/passwords_data'
|
3
|
+
require 'gtk2'
|
4
|
+
require 'find'
|
5
|
+
|
6
|
+
class Gtk2PasswordApp
|
7
|
+
include Configuration
|
8
|
+
|
9
|
+
ABOUT = {
|
10
|
+
'authors' => ['carlosjhr64@gmail.com'],
|
11
|
+
'comments' => "Ruby-Gtk2 Password Manager.",
|
12
|
+
'version' => $version,
|
13
|
+
'website' => 'http://ruby-gnome-apps.blogspot.com/search/label/Passwords',
|
14
|
+
'website-label' => 'Ruby Gnome Password Manager',
|
15
|
+
'license' => 'GPL',
|
16
|
+
'copyright' => '$Date: 2009/02/19 20:50:22 $'.gsub(/\s*\$\s*/,''),
|
17
|
+
'logo' => Gdk::Pixbuf.new(LOGO_IMAGE),
|
18
|
+
}
|
19
|
+
|
20
|
+
BUTTONS = [[ :username, :current, :url, ],[ :note, :edit, :quit, ],]
|
21
|
+
|
22
|
+
EDITOR_LABELS = [
|
23
|
+
:account,
|
24
|
+
:url,
|
25
|
+
:note,
|
26
|
+
:username,
|
27
|
+
:password,
|
28
|
+
]
|
29
|
+
|
30
|
+
|
31
|
+
EDITOR_BUTTONS = [
|
32
|
+
[ :random, :alphanum, :num, :alpha, :caps, ],
|
33
|
+
[ :visibility, :current, :previous, :cancel, :save, :update, ],
|
34
|
+
[ :delete, :cpwd, :cpph ],
|
35
|
+
]
|
36
|
+
|
37
|
+
TEXT = {
|
38
|
+
# Labels
|
39
|
+
:account => 'Account',
|
40
|
+
:note => 'Note',
|
41
|
+
:password => 'New',
|
42
|
+
# Buttons
|
43
|
+
:username => 'Username',
|
44
|
+
:current => 'Current',
|
45
|
+
:url => 'Url',
|
46
|
+
:note => 'Note',
|
47
|
+
:edit => 'Edit',
|
48
|
+
:update => 'Update',
|
49
|
+
:visibility => 'Visible',
|
50
|
+
:alphanum => 'Alpha-Numeric',
|
51
|
+
:num => 'Numeric',
|
52
|
+
:alpha => 'Letters',
|
53
|
+
:caps => 'All-Caps',
|
54
|
+
:random => 'Random',
|
55
|
+
:previous => 'Previous',
|
56
|
+
:quit => 'Quit',
|
57
|
+
:cancel => 'Cancel',
|
58
|
+
:save => 'Save',
|
59
|
+
:cpwd => 'Data File Password',
|
60
|
+
:cpph => 'Data File Passphrase',
|
61
|
+
:delete => 'Delete Account',
|
62
|
+
}
|
63
|
+
|
64
|
+
FONT = Pango::FontDescription.new(FONT_NAME)
|
65
|
+
RED = Gdk::Color.parse("#A00000")
|
66
|
+
BLACK = Gdk::Color.parse("#000000")
|
67
|
+
|
68
|
+
def quit_windows
|
69
|
+
if @editing
|
70
|
+
@editing.hide
|
71
|
+
@editing.destroy
|
72
|
+
@editing = nil
|
73
|
+
end
|
74
|
+
if @window
|
75
|
+
@window.hide
|
76
|
+
@window.destroy
|
77
|
+
@window = nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def quick_message(message, window, title='Note', font=FONT)
|
82
|
+
# Create the dialog
|
83
|
+
dialog = Gtk::Dialog.new(
|
84
|
+
title,
|
85
|
+
window, Gtk::Dialog::DESTROY_WITH_PARENT,
|
86
|
+
[ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_NONE ])
|
87
|
+
|
88
|
+
# Ensure that the dialog box is destroyed when the user responds.
|
89
|
+
dialog.signal_connect('response') { dialog.destroy }
|
90
|
+
|
91
|
+
# Add the message in a label, and show everything we've added to the dialog.
|
92
|
+
label = Gtk::Label.new(message)
|
93
|
+
label.wrap = true
|
94
|
+
label.modify_font(font) if font
|
95
|
+
dialog.vbox.add(label)
|
96
|
+
dialog.show_all
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_salt(title='Short Password')
|
100
|
+
dialog = Gtk::Dialog.new(
|
101
|
+
title,
|
102
|
+
nil, nil,
|
103
|
+
[ Gtk::Stock::QUIT, 0 ],
|
104
|
+
[ Gtk::Stock::OK, 1 ])
|
105
|
+
|
106
|
+
label = Gtk::Label.new(title)
|
107
|
+
label.justify = Gtk::JUSTIFY_LEFT
|
108
|
+
label.wrap = true
|
109
|
+
label.modify_font(FONT)
|
110
|
+
dialog.vbox.add(label)
|
111
|
+
entry = Gtk::Entry.new
|
112
|
+
entry.visibility = false
|
113
|
+
entry.modify_font(FONT)
|
114
|
+
dialog.vbox.add(entry)
|
115
|
+
dialog.show_all
|
116
|
+
|
117
|
+
entry.signal_connect('activate'){
|
118
|
+
dialog.response(1)
|
119
|
+
}
|
120
|
+
|
121
|
+
ret = nil
|
122
|
+
dialog.run {|response|
|
123
|
+
ret = entry.text.strip if response == 1
|
124
|
+
}
|
125
|
+
dialog.destroy
|
126
|
+
|
127
|
+
return ret
|
128
|
+
end
|
129
|
+
|
130
|
+
def _create_passphrase(pfile)
|
131
|
+
passphrase = ''
|
132
|
+
|
133
|
+
56.times do
|
134
|
+
passphrase += (rand(94)+33).chr
|
135
|
+
end
|
136
|
+
File.open(pfile,'w'){|fh| fh.write passphrase }
|
137
|
+
File.chmod(0600, pfile)
|
138
|
+
|
139
|
+
return passphrase
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_passphrase(mv=false)
|
143
|
+
passphrase = ''
|
144
|
+
|
145
|
+
pfile = USER_CONF_DIR+'/passphrase.txt'
|
146
|
+
if mv then
|
147
|
+
File.rename(pfile, pfile+'.bak') if File.exist?(pfile)
|
148
|
+
passphrase = _create_passphrase(pfile)
|
149
|
+
else
|
150
|
+
if File.exist?(pfile) then
|
151
|
+
File.open(pfile,'r'){|fh| passphrase = fh.read }
|
152
|
+
else
|
153
|
+
passphrase = _create_passphrase(pfile)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
return passphrase
|
158
|
+
end
|
159
|
+
|
160
|
+
def has_datafile?
|
161
|
+
Find.find(USER_CONF_DIR){|fn|
|
162
|
+
Find.prune if !(fn==USER_CONF_DIR) && File.directory?(fn)
|
163
|
+
if fn =~/[0123456789abcdef]{32}\.dat$/ then
|
164
|
+
return true
|
165
|
+
end
|
166
|
+
}
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
|
170
|
+
def initialize
|
171
|
+
@updated = false # only saves data if data updated
|
172
|
+
@editing = nil # when editor window is up, this is set.
|
173
|
+
@verified = Time.now.to_i
|
174
|
+
|
175
|
+
@pwd = get_salt || exit
|
176
|
+
@pph = get_passphrase
|
177
|
+
@passwords = PasswordsData.new(@pwd+@pph)
|
178
|
+
# Password file exist?
|
179
|
+
if @passwords.exist? # then
|
180
|
+
# Yes, load passwords file.
|
181
|
+
@passwords.load
|
182
|
+
else
|
183
|
+
# No, check if there is a file....
|
184
|
+
if has_datafile? # then
|
185
|
+
# Yes, it's got a datafile. Ask for password again.
|
186
|
+
while !@passwords.exist? do
|
187
|
+
@pwd = get_salt('Try again!') || exit
|
188
|
+
@passwords = PasswordsData.new(@pwd+@pph)
|
189
|
+
end
|
190
|
+
@passwords.load
|
191
|
+
else
|
192
|
+
# Else, must be a new intall.
|
193
|
+
pwd = @pwd
|
194
|
+
@pwd = get_salt('Verify New Password') || exit
|
195
|
+
while !(pwd == @pwd) do
|
196
|
+
pwd = get_salt('Try again!') || exit
|
197
|
+
@pwd = get_salt('Verify New Password') || exit
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# Off to the races...
|
202
|
+
end
|
203
|
+
|
204
|
+
def verify_user
|
205
|
+
now = Time.now.to_i
|
206
|
+
if now - @verified > VERIFIED_EXPIRED then
|
207
|
+
pwd0 = get_salt('Current Password')
|
208
|
+
return false if !pwd0
|
209
|
+
tries = 1
|
210
|
+
while !(pwd0==@pwd) do
|
211
|
+
tries += 1
|
212
|
+
pwd0 = get_salt('CURRENT PASSWORD???')
|
213
|
+
return false if !pwd0 || tries > 2
|
214
|
+
end
|
215
|
+
end
|
216
|
+
@verified = now
|
217
|
+
return true
|
218
|
+
end
|
219
|
+
|
220
|
+
def edit(combo_box, index)
|
221
|
+
begin
|
222
|
+
window = @editing
|
223
|
+
window.signal_connect('delete_event') { @editing = nil }
|
224
|
+
old_list = @passwords.accounts # dup not necessary
|
225
|
+
|
226
|
+
vbox = Gtk::VBox.new
|
227
|
+
window.add(vbox)
|
228
|
+
|
229
|
+
pwdlength = Gtk::SpinButton.new(MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, 1)
|
230
|
+
pwdlength.value = DEFAULT_PASSWORD_LENGTH
|
231
|
+
pwdlength.width_request = SPIN_BUTTON_LENGTH
|
232
|
+
|
233
|
+
widget = {}
|
234
|
+
EDITOR_LABELS.each {|s|
|
235
|
+
hbox = Gtk::HBox.new
|
236
|
+
label = Gtk::Label.new(TEXT[s]+':')
|
237
|
+
label.modify_font(FONT)
|
238
|
+
label.width_request = LABEL_WIDTH
|
239
|
+
label.justify = Gtk::JUSTIFY_RIGHT
|
240
|
+
label.wrap = true
|
241
|
+
widget[s] = (s==:account)? Gtk::ComboBoxEntry.new : Gtk::Entry.new
|
242
|
+
widget[s].width_request = ENTRY_WIDTH - ((s == :password)? SPIN_BUTTON_LENGTH+2*PAD: 0)
|
243
|
+
widget[s].modify_font(FONT)
|
244
|
+
hbox.pack_start(label, false, false, PAD)
|
245
|
+
hbox.pack_end(pwdlength, false, false, PAD) if s == :password
|
246
|
+
hbox.pack_end(widget[s], false, false, PAD)
|
247
|
+
vbox.pack_start(hbox, false, false, PAD)
|
248
|
+
}
|
249
|
+
|
250
|
+
EDITOR_BUTTONS.each{|row|
|
251
|
+
hbox = Gtk::HBox.new
|
252
|
+
row.each {|s|
|
253
|
+
widget[s] = Gtk::Button.new(TEXT[s])
|
254
|
+
widget[s].child.modify_font(FONT)
|
255
|
+
(s==:cancel || s==:save || s==:update)?
|
256
|
+
hbox.pack_end(widget[s], false, false, PAD) :
|
257
|
+
hbox.pack_start(widget[s], false, false, PAD)
|
258
|
+
}
|
259
|
+
vbox.pack_start(hbox, false, false, PAD)
|
260
|
+
}
|
261
|
+
|
262
|
+
# Account
|
263
|
+
@passwords.accounts.each { |account|
|
264
|
+
widget[:account].append_text( account )
|
265
|
+
}
|
266
|
+
widget[:account].active = index if index
|
267
|
+
account_changed = proc {
|
268
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
269
|
+
if account.length > 0 then
|
270
|
+
widget[:password].text = ''
|
271
|
+
if @passwords.include?(account) then
|
272
|
+
widget[:url].text = @passwords.url_of(account)
|
273
|
+
widget[:note].text = @passwords.note_of(account)
|
274
|
+
widget[:username].text = @passwords.username_of(account)
|
275
|
+
else
|
276
|
+
widget[:url].text = ''
|
277
|
+
widget[:note].text = ''
|
278
|
+
widget[:username].text = ''
|
279
|
+
end
|
280
|
+
end
|
281
|
+
}
|
282
|
+
account_changed.call
|
283
|
+
widget[:account].signal_connect('changed'){
|
284
|
+
account_changed.call
|
285
|
+
}
|
286
|
+
|
287
|
+
# New Password
|
288
|
+
widget[:password].visibility = false
|
289
|
+
|
290
|
+
# Update
|
291
|
+
widget[:update].signal_connect('clicked'){
|
292
|
+
url = widget[:url].text.strip
|
293
|
+
if url.length == 0 || url =~ URL_PATTERN then
|
294
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
295
|
+
if account.length > 0 then
|
296
|
+
@updated = true if !@updated
|
297
|
+
if !@passwords.include?(account) then
|
298
|
+
@passwords.add(account)
|
299
|
+
i = @passwords.accounts.index(account)
|
300
|
+
widget[:account].insert_text(i,account)
|
301
|
+
end
|
302
|
+
@passwords.url_of(account, url)
|
303
|
+
@passwords.note_of(account, widget[:note].text.strip)
|
304
|
+
@passwords.username_of(account, widget[:username].text.strip)
|
305
|
+
password = widget[:password].text.strip
|
306
|
+
if password.length > 0 then
|
307
|
+
@passwords.password_of(account, password) if !@passwords.verify?(account, password)
|
308
|
+
widget[:password].text = ''
|
309
|
+
end
|
310
|
+
end
|
311
|
+
else
|
312
|
+
quick_message('Need url like http://www.site.com/page.html', window)
|
313
|
+
end
|
314
|
+
}
|
315
|
+
|
316
|
+
# Random
|
317
|
+
widget[:random].signal_connect('clicked'){
|
318
|
+
suggestion = ''
|
319
|
+
pwdlength.value.to_i.times do
|
320
|
+
suggestion += (rand(94)+33).chr
|
321
|
+
end
|
322
|
+
widget[:password].text = suggestion
|
323
|
+
}
|
324
|
+
# Alpha-Numeric
|
325
|
+
widget[:alphanum].signal_connect('clicked'){
|
326
|
+
suggestion = ''
|
327
|
+
while suggestion.length < pwdlength.value.to_i do
|
328
|
+
chr = (rand(75)+48).chr
|
329
|
+
suggestion += chr if chr =~/\w/
|
330
|
+
end
|
331
|
+
widget[:password].text = suggestion
|
332
|
+
}
|
333
|
+
# Numeric
|
334
|
+
widget[:num].signal_connect('clicked'){
|
335
|
+
suggestion = ''
|
336
|
+
pwdlength.value.to_i.times do
|
337
|
+
chr = (rand(10)+48).chr
|
338
|
+
suggestion += chr
|
339
|
+
end
|
340
|
+
widget[:password].text = suggestion
|
341
|
+
}
|
342
|
+
# Letters
|
343
|
+
widget[:alpha].signal_connect('clicked'){
|
344
|
+
suggestion = ''
|
345
|
+
while suggestion.length < pwdlength.value.to_i do
|
346
|
+
chr = (rand(58)+65).chr
|
347
|
+
suggestion += chr if chr =~/[A-Z]/i
|
348
|
+
end
|
349
|
+
widget[:password].text = suggestion
|
350
|
+
}
|
351
|
+
# Caps
|
352
|
+
widget[:caps].signal_connect('clicked'){
|
353
|
+
suggestion = ''
|
354
|
+
pwdlength.value.to_i.times do
|
355
|
+
chr = (rand(26)+65).chr
|
356
|
+
suggestion += chr
|
357
|
+
end
|
358
|
+
widget[:password].text = suggestion
|
359
|
+
}
|
360
|
+
|
361
|
+
# Visibility
|
362
|
+
widget[:visibility].signal_connect('clicked'){
|
363
|
+
widget[:password].visibility = !widget[:password].visibility?
|
364
|
+
}
|
365
|
+
|
366
|
+
# Current
|
367
|
+
widget[:current].signal_connect('clicked'){
|
368
|
+
primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
369
|
+
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
370
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
371
|
+
primary.text = clipboard.text = @passwords.password_of(account)
|
372
|
+
}
|
373
|
+
|
374
|
+
# Previous
|
375
|
+
widget[:previous].signal_connect('clicked'){
|
376
|
+
primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
377
|
+
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
378
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
379
|
+
primary.text = clipboard.text = @passwords.previous_password_of(account)
|
380
|
+
}
|
381
|
+
|
382
|
+
# Change Password
|
383
|
+
widget[:cpwd].signal_connect('clicked'){
|
384
|
+
if verify_user then
|
385
|
+
pwd1 = get_salt('New Password') || return
|
386
|
+
pwd2 = get_salt('Verify') || return
|
387
|
+
while !(pwd1==pwd2) do
|
388
|
+
pwd1 = get_salt('Try again!') || return
|
389
|
+
pwd2 = get_salt('Verify') || return
|
390
|
+
end
|
391
|
+
@pwd = pwd1
|
392
|
+
@passwords.save(@pwd+@pph)
|
393
|
+
else
|
394
|
+
quit_windows
|
395
|
+
end
|
396
|
+
}
|
397
|
+
|
398
|
+
# Change Passphrase
|
399
|
+
widget[:cpph].signal_connect('clicked'){
|
400
|
+
if verify_user then
|
401
|
+
@pph = get_passphrase(true) # mv old passphrase? true
|
402
|
+
@passwords.save(@pwd+@pph)
|
403
|
+
else
|
404
|
+
quit_windows
|
405
|
+
end
|
406
|
+
}
|
407
|
+
|
408
|
+
# Save
|
409
|
+
widget[:save].signal_connect('clicked'){
|
410
|
+
window.hide
|
411
|
+
if @updated then
|
412
|
+
if verify_user then
|
413
|
+
@passwords.save
|
414
|
+
@updated = false
|
415
|
+
new_list = @passwords.accounts # dup not needed
|
416
|
+
new_list.each {|account|
|
417
|
+
if !old_list.include?(account) then
|
418
|
+
i = new_list.index(account)
|
419
|
+
old_list.insert(i,account)
|
420
|
+
combo_box.insert_text(i,account)
|
421
|
+
end
|
422
|
+
}
|
423
|
+
old_list.each {|account|
|
424
|
+
if !new_list.include?(account) then
|
425
|
+
i = old_list.index(account)
|
426
|
+
old_list.delete_at(i)
|
427
|
+
combo_box.remove_text(i)
|
428
|
+
end
|
429
|
+
}
|
430
|
+
else
|
431
|
+
quit_windows
|
432
|
+
end
|
433
|
+
end
|
434
|
+
window.destroy
|
435
|
+
@editing = nil
|
436
|
+
}
|
437
|
+
|
438
|
+
# Delete
|
439
|
+
widget[:delete].signal_connect('clicked'){
|
440
|
+
account = (widget[:account].active_text)? widget[:account].active_text.strip: ''
|
441
|
+
i = @passwords.accounts.index(account)
|
442
|
+
@passwords.delete(account)
|
443
|
+
widget[:account].remove_text(i)
|
444
|
+
@updated = true
|
445
|
+
}
|
446
|
+
|
447
|
+
# Cancel
|
448
|
+
widget[:cancel].signal_connect('clicked'){
|
449
|
+
window.hide
|
450
|
+
if @updated then
|
451
|
+
@passwords.load # revert
|
452
|
+
@updated = false
|
453
|
+
end
|
454
|
+
window.destroy
|
455
|
+
@editing = nil
|
456
|
+
}
|
457
|
+
|
458
|
+
window.show_all
|
459
|
+
rescue Exception
|
460
|
+
puts_bang!
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def run
|
465
|
+
begin
|
466
|
+
@window = window = Gtk::Window.new
|
467
|
+
window.signal_connect('delete_event') { quit_windows }
|
468
|
+
|
469
|
+
vbox = Gtk::VBox.new
|
470
|
+
window.add(vbox)
|
471
|
+
|
472
|
+
combo_box =Gtk::ComboBox.new
|
473
|
+
combo_box.modify_font(FONT)
|
474
|
+
vbox.pack_start(combo_box, false, false, PAD)
|
475
|
+
|
476
|
+
button = {}
|
477
|
+
|
478
|
+
BUTTONS.each{ |row|
|
479
|
+
hbox = Gtk::HBox.new
|
480
|
+
row.each{|b|
|
481
|
+
button[b] = Gtk::Button.new(TEXT[b])
|
482
|
+
button[b].modify_font(FONT)
|
483
|
+
button[b].width_request = LABEL_WIDTH
|
484
|
+
hbox.pack_start(button[b], false, false, PAD)
|
485
|
+
}
|
486
|
+
vbox.pack_start(hbox, false, false, PAD)
|
487
|
+
}
|
488
|
+
|
489
|
+
@passwords.accounts.each { |account|
|
490
|
+
combo_box.append_text( account )
|
491
|
+
}
|
492
|
+
combo_box.active = 0
|
493
|
+
button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(combo_box.active_text.strip))? RED: BLACK) if combo_box.active_text
|
494
|
+
combo_box.signal_connect('changed'){
|
495
|
+
button[:edit].child.modify_fg(Gtk::STATE_NORMAL, (@passwords.expired?(combo_box.active_text.strip))? RED: BLACK)
|
496
|
+
}
|
497
|
+
button[:username].signal_connect('clicked'){
|
498
|
+
primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
499
|
+
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
500
|
+
account = (combo_box.active_text)? combo_box.active_text.strip: ''
|
501
|
+
primary.text = clipboard.text = @passwords.username_of(account)
|
502
|
+
}
|
503
|
+
button[:current].signal_connect('clicked'){
|
504
|
+
primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
505
|
+
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
506
|
+
account = (combo_box.active_text)? combo_box.active_text.strip: ''
|
507
|
+
primary.text = clipboard.text = @passwords.password_of(account)
|
508
|
+
}
|
509
|
+
button[:url].signal_connect('clicked'){
|
510
|
+
account = (combo_box.active_text)? combo_box.active_text.strip: ''
|
511
|
+
url = @passwords.url_of(account)
|
512
|
+
if url.length > 0 && url =~ URL_PATTERN then
|
513
|
+
system("#{BROWSER} '#{url}' > /dev/null 2>&1 &")
|
514
|
+
end
|
515
|
+
}
|
516
|
+
button[:note].signal_connect('clicked'){
|
517
|
+
account = (combo_box.active_text)? combo_box.active_text.strip: ''
|
518
|
+
note = @passwords.note_of(account).strip
|
519
|
+
note = '*** empty note ***' if note.length == 0
|
520
|
+
quick_message(note,window)
|
521
|
+
}
|
522
|
+
button[:edit].signal_connect('clicked'){
|
523
|
+
if !@editing then
|
524
|
+
account = (combo_box.active_text)? combo_box.active_text.strip: ''
|
525
|
+
i = @passwords.accounts.index(account)
|
526
|
+
@editing = Gtk::Window.new
|
527
|
+
edit(combo_box,i)
|
528
|
+
end
|
529
|
+
}
|
530
|
+
button[:quit].signal_connect('clicked'){ quit_windows }
|
531
|
+
|
532
|
+
if !@passwords.exist? then
|
533
|
+
@editing = Gtk::Window.new
|
534
|
+
edit(combo_box,0)
|
535
|
+
end
|
536
|
+
|
537
|
+
window.show_all
|
538
|
+
rescue Exception
|
539
|
+
puts_bang!
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def status_icon
|
544
|
+
icon = Gtk::StatusIcon.new
|
545
|
+
icon.set_icon_name(Gtk::Stock::DIALOG_AUTHENTICATION)
|
546
|
+
icon.tooltip = 'Password Manager'
|
547
|
+
icon.signal_connect('activate') {
|
548
|
+
if @window then
|
549
|
+
quit_windows
|
550
|
+
else
|
551
|
+
menu = Gtk::Menu.new
|
552
|
+
@passwords.accounts.each {|account|
|
553
|
+
menuitem = Gtk::MenuItem.new(account)
|
554
|
+
menuitem.child.modify_fg(Gtk::STATE_NORMAL, RED) if @passwords.expired?(account)
|
555
|
+
menu.append(menuitem)
|
556
|
+
menuitem.signal_connect('activate'){|b|
|
557
|
+
primary = Gtk::Clipboard.get(Gdk::Selection::PRIMARY)
|
558
|
+
clipboard = Gtk::Clipboard.get(Gdk::Selection::CLIPBOARD)
|
559
|
+
primary.text = clipboard.text = @passwords.password_of(b.child.text.strip)
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
menu.append( Gtk::SeparatorMenuItem.new )
|
564
|
+
|
565
|
+
menuitem = Gtk::MenuItem.new('Quit')
|
566
|
+
menuitem.signal_connect('activate'){
|
567
|
+
quit_windows
|
568
|
+
icon.set_visible(false)
|
569
|
+
icon = nil
|
570
|
+
Gtk.main_quit
|
571
|
+
}
|
572
|
+
menu.append(menuitem)
|
573
|
+
|
574
|
+
Gtk::AboutDialog.set_url_hook{|about,link| system( "#{BROWSER} '#{link}' > /dev/null 2>&1 &" ) }
|
575
|
+
menuitem = Gtk::MenuItem.new('About')
|
576
|
+
menuitem.signal_connect('activate'){
|
577
|
+
Gtk::AboutDialog.show(nil, ABOUT)
|
578
|
+
}
|
579
|
+
menu.append(menuitem)
|
580
|
+
|
581
|
+
menuitem = Gtk::MenuItem.new('Run')
|
582
|
+
menuitem.signal_connect('activate'){
|
583
|
+
if !@window
|
584
|
+
verify_user
|
585
|
+
run
|
586
|
+
end
|
587
|
+
}
|
588
|
+
menu.append(menuitem)
|
589
|
+
|
590
|
+
menu.show_all
|
591
|
+
menu.popup(nil, nil, 0, 0)
|
592
|
+
end
|
593
|
+
}
|
594
|
+
run if !@passwords.exist?
|
595
|
+
Gtk.main
|
596
|
+
end
|
597
|
+
end
|
data/lib/iocrypt.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# $Date: 2009/02/18 00:41:54 $
|
2
|
+
require 'yaml'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'crypt/blowfish'
|
5
|
+
|
6
|
+
class IOCrypt
|
7
|
+
def initialize(passphrase)
|
8
|
+
@blowfish = Crypt::Blowfish.new(passphrase[0..55])
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(dumpfile)
|
12
|
+
data = nil
|
13
|
+
|
14
|
+
File.open(dumpfile,'r'){|fh|
|
15
|
+
data = YAML.load( @blowfish.decrypt_string( fh.read ) )
|
16
|
+
}
|
17
|
+
|
18
|
+
return data
|
19
|
+
end
|
20
|
+
|
21
|
+
def dump(dumpfile, data)
|
22
|
+
count = nil
|
23
|
+
|
24
|
+
File.open(dumpfile,'w') do |fh|
|
25
|
+
count = fh.write( @blowfish.encrypt_string( YAML.dump( data ) ) )
|
26
|
+
end
|
27
|
+
|
28
|
+
return count
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# $Date: 2009/02/18 16:07:01 $
|
2
|
+
require 'lib/iocrypt'
|
3
|
+
require 'digest/md5'
|
4
|
+
|
5
|
+
class PasswordsData
|
6
|
+
include Configuration
|
7
|
+
attr_accessor :account
|
8
|
+
attr_reader :data
|
9
|
+
|
10
|
+
PASSWORD = 0
|
11
|
+
PREVIOUS = 1
|
12
|
+
NOTE = 2
|
13
|
+
USERNAME = 3
|
14
|
+
URL = 4
|
15
|
+
LAST_UPDATE = 5
|
16
|
+
|
17
|
+
def _reset(passphrase)
|
18
|
+
raise "Need a good passphrase" if !passphrase || passphrase.length < 7
|
19
|
+
@passphrase = passphrase[0..55]
|
20
|
+
@dumpfile = USER_CONF_DIR + '/' + Digest::MD5.hexdigest(@passphrase) + '.dat'
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(passphrase)
|
24
|
+
_reset(passphrase)
|
25
|
+
@data = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def exist?
|
29
|
+
File.exist?(@dumpfile)
|
30
|
+
end
|
31
|
+
|
32
|
+
def load(passphrase = nil)
|
33
|
+
_reset(passphrase) if passphrase
|
34
|
+
raise "Wrong passphrase" if !exist?
|
35
|
+
iocrypt = IOCrypt.new(@passphrase)
|
36
|
+
@data = iocrypt.load(@dumpfile)
|
37
|
+
end
|
38
|
+
|
39
|
+
def save(passphrase = nil)
|
40
|
+
# just in case, keep a backup
|
41
|
+
File.rename(@dumpfile, @dumpfile+'.bak') if File.exist?(@dumpfile)
|
42
|
+
_reset(passphrase) if passphrase
|
43
|
+
iocrypt = IOCrypt.new(@passphrase)
|
44
|
+
iocrypt.dump(@dumpfile, @data)
|
45
|
+
File.chmod(0600, @dumpfile)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add(account)
|
49
|
+
raise "Pre-existing" if @data[account]
|
50
|
+
raise "Can't have nil account" if !account
|
51
|
+
@data[account] = ['','','','','']
|
52
|
+
end
|
53
|
+
|
54
|
+
def accounts
|
55
|
+
@data.keys.sort
|
56
|
+
end
|
57
|
+
|
58
|
+
def include?(account)
|
59
|
+
return (@data[account])? true: false
|
60
|
+
end
|
61
|
+
|
62
|
+
def verify?(account,password)
|
63
|
+
return @data[account][PASSWORD] == password
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete(account)
|
67
|
+
raise "#{account} not found" if !@data[account]
|
68
|
+
@data.delete(account)
|
69
|
+
end
|
70
|
+
|
71
|
+
def url_of(account, url=nil)
|
72
|
+
raise "#{account} not found" if !@data[account]
|
73
|
+
@data[account][URL] = url if url
|
74
|
+
return @data[account][URL] || ''
|
75
|
+
end
|
76
|
+
|
77
|
+
def expired?(account)
|
78
|
+
raise "#{account} not found" if !@data[account]
|
79
|
+
return true if !@data[account][LAST_UPDATE] || ((Time.now.to_i - @data[account][LAST_UPDATE]) > PASSWORD_EXPIRED)
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
|
83
|
+
def password_of(account, p=nil)
|
84
|
+
raise "#{account} not found" if !@data[account]
|
85
|
+
if p then
|
86
|
+
@data[account][PREVIOUS] = @data[account][PASSWORD]
|
87
|
+
@data[account][PASSWORD] = p
|
88
|
+
@data[account][LAST_UPDATE] = Time.now.to_i
|
89
|
+
end
|
90
|
+
return @data[account][PASSWORD] || ''
|
91
|
+
end
|
92
|
+
|
93
|
+
# previous password
|
94
|
+
def previous_password_of(account)
|
95
|
+
raise "#{account} not found" if !@data[account]
|
96
|
+
return @data[account][PREVIOUS] || ''
|
97
|
+
end
|
98
|
+
|
99
|
+
def note_of(account, n=nil)
|
100
|
+
raise "#{account} not found" if !@data[account]
|
101
|
+
@data[account][NOTE] = n if n
|
102
|
+
return @data[account][NOTE] || ''
|
103
|
+
end
|
104
|
+
|
105
|
+
def username_of(account, usr=nil)
|
106
|
+
raise "#{account} not found" if !@data[account]
|
107
|
+
@data[account][USERNAME] = usr if usr
|
108
|
+
return @data[account][USERNAME] || ''
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# $Date: 2009/02/19 15:49:32 $
|
2
|
+
module UserSpace
|
3
|
+
# provided by caller (global_options_variables):
|
4
|
+
# USER_CONF_DIR
|
5
|
+
# GEM_LIB_DIR
|
6
|
+
# CONF_FILE
|
7
|
+
|
8
|
+
def self.mkdir(subdir='')
|
9
|
+
dir = USER_CONF_DIR+subdir
|
10
|
+
Dir.mkdir(dir) if !File.exist?(dir)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.cp(rfile,wfile)
|
14
|
+
if !File.exist?(wfile) then
|
15
|
+
File.open(rfile, 'r'){|fhr|
|
16
|
+
File.open(wfile, 'w'){|fhw|
|
17
|
+
fhr.each{|ln| fhw.puts ln}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
File.chmod(0600, wfile) # rw to user only
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.copy(rfile, wfile=nil)
|
25
|
+
wfile = rfile if !wfile
|
26
|
+
self.cp(GEM_ROOT_DIR+rfile, USER_CONF_DIR+wfile)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.cp_conf
|
30
|
+
self.cp(GEM_LIB_DIR+CONF_FILE, USER_CONF_DIR+CONF_FILE)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.setup
|
34
|
+
self.mkdir
|
35
|
+
self.cp_conf
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gtk2passwordapp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- carlosjhr64@gmail.com
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-15 00:00:00 -08:00
|
13
|
+
default_executable: gtk2youtubeapp
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: crypt
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: A Ruby-Gtk2 application to keep passwords. Uses gem crypt/blofish to encrypt data. Requires crypt and gtk2.
|
26
|
+
email: carlosjhr64@gmail.com
|
27
|
+
executables:
|
28
|
+
- gtk2passwordapp
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- lib/configuration.rb
|
35
|
+
- lib/global_options_variables.rb
|
36
|
+
- lib/gtk2passwordapp.rb
|
37
|
+
- lib/iocrypt.rb
|
38
|
+
- lib/passwords_data.rb
|
39
|
+
- lib/setup_user_space.rb
|
40
|
+
- gifs/logo.gif
|
41
|
+
- README.txt
|
42
|
+
has_rdoc: false
|
43
|
+
homepage: http://ruby-gnome-apps.blogspot.com/search/label/Password
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- .
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements:
|
62
|
+
- ruby-gtk2
|
63
|
+
rubyforge_project: gtk2passwordapp
|
64
|
+
rubygems_version: 1.3.1
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: Ruby-Gtk2 application to maitain passwords.
|
68
|
+
test_files: []
|
69
|
+
|