gtk2passwordapp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,8 @@
1
+ Just run:
2
+ gtk2passwordapp
3
+
4
+ For some documentation on usage, see
5
+
6
+ http://ruby-gnome-apps.blogspot.com/search/label/Passwords
7
+
8
+ -Carlos
@@ -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
+