pws 0.1.0.pre

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.
Files changed (8) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +20 -0
  4. data/Rakefile +17 -0
  5. data/VERSION +1 -0
  6. data/bin/pws +244 -0
  7. data/pws.gemspec +44 -0
  8. metadata +76 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Jan Lelis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pws"
8
+ gem.summary = %Q{pws is a little PasswordSafe}
9
+ gem.email = "mail@janlelis.de"
10
+ gem.homepage = "http://rbjl.net/41"
11
+ gem.authors = ["Jan Lelis"]
12
+ gem.executables = %w[pws]
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
17
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0.pre
data/bin/pws ADDED
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems' if RUBY_VERSION[2] == ?8
4
+
5
+ require 'openssl'
6
+ require 'fileutils'
7
+ require 'clipboard' # gem install clipboard
8
+ require 'zucker/alias_for' # gem install zucker
9
+ require 'zucker/kernel'
10
+ require 'zucker/version'
11
+
12
+ class PasswordSafe
13
+ VERSION = "0.1.0".freeze
14
+
15
+ Entry = Struct.new :description, :password
16
+
17
+ class NoAccess < StandardError; end
18
+
19
+ # Creates a new password safe. Takes the path to the password file, by default: ~/.pws
20
+ def initialize( filename = File.expand_path('~/.pws') )
21
+ @pwfile = filename
22
+
23
+ access_safe
24
+ read_safe
25
+ end
26
+
27
+ # Add a password entry, params: name, description (optional), password (optional, opens prompt if not given)
28
+ def add(key, description = nil, password = nil)
29
+ if @pwdata[key]
30
+ print "Do you really want to overwrite the exisiting password entry? (press <Enter> to proceed)"
31
+ return unless (a = $stdin.getc) == 10 || a == 13
32
+ else
33
+ @pwdata[key] = Entry.new
34
+ end
35
+ @pwdata[key].password = password || ask_for_password( "please enter a password for #{key}" )
36
+ @pwdata[key].description = description
37
+ write_safe
38
+
39
+ puts "The password safe has been updated"
40
+ end
41
+ aliases_for :add, :a, :set, :create, :update, :[]= # using zucker/alias_for
42
+
43
+ # Gets the password entry and copies it to the clipboard. The second parameter is the time in seconds it stays there
44
+ def get(key, seconds = 20)
45
+ if pw_plaintext = @pwdata[key] && @pwdata[key].password
46
+ Clipboard.copy pw_plaintext
47
+ if seconds && seconds.to_i > 0
48
+ puts "The password is available in your clipboard for #{seconds.to_i} seconds"
49
+ sleep seconds.to_i
50
+ Clipboard.clear
51
+ else
52
+ puts "The password has been copied to your clipboard"
53
+ end
54
+ else
55
+ puts "No password entry found for #{key}"
56
+ end
57
+ end
58
+ aliases_for :get, :g, :entry, :[]
59
+
60
+ # Removes a specific password entry
61
+ def remove(key)
62
+ if @pwdata.delete key
63
+ puts "#{key} has been removed"
64
+ else
65
+ puts "Nothing removed"
66
+ end
67
+ end
68
+ aliases_for :remove, :r, :delete
69
+
70
+ # Shows a password entry list
71
+ def show
72
+ puts "Available passwords \n" +
73
+
74
+ if @pwdata.empty?
75
+ ' (none)'
76
+ else
77
+ @pwdata.map{ |key, pwentry|
78
+ " #{key}" + if pwentry.description then ": #{pwentry.description}" else '' end
79
+ }*"\n"
80
+ end
81
+ end
82
+ aliases_for :show, :s, :list
83
+
84
+ # Shows descriptions of some password entries
85
+ def description(*keys)
86
+ keys.each{ |key|
87
+ if @pwdata[key]
88
+ puts "#{key}: #{@pwdata[key].description || key}"
89
+ else
90
+ puts "No password entry found for #{key}"
91
+ end
92
+ }
93
+ end
94
+
95
+ # Changes the master password
96
+ def master
97
+ @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
98
+ write_safe
99
+ puts 'The master password has been changed'
100
+ end
101
+ aliases_for :master, :m
102
+
103
+ # Adds a password entry with a fresh generated random password
104
+ def generate( key, description = nil, length = 128, chars = (32..126).map(&:chr) )
105
+ add key, (1..length).map{ chars.sample }.join
106
+ end
107
+
108
+ # Prevents accidental displaying, e.g. in irb
109
+ def to_s
110
+ '#<just another password safe>'
111
+ end
112
+ alias_for :to_s, :inspect
113
+
114
+ private
115
+
116
+ # Tries to load and decrypt the password safe from the pwfile
117
+ def read_safe
118
+ pwdata_encrypted = File.read @pwfile
119
+ pwdata_dump = Encryptor.decrypt( pwdata_encrypted, @pwhash )
120
+ @pwdata = remove_dummy_data( Marshal.load(pwdata_dump) ) || {}
121
+ rescue
122
+ fail NoAccess, 'Could not decrypt/load the password safe!'
123
+ end
124
+
125
+ # Tries to encrypt and save the password safe into the pwfile
126
+ def write_safe
127
+ pwdata_dump = Marshal.dump add_dummy_data( @pwdata || {} )
128
+ pwdata_encrypted = Encryptor.encrypt pwdata_dump, @pwhash
129
+ File.open( @pwfile, 'w' ){ |f| f.write pwdata_encrypted }
130
+ rescue
131
+ fail NoAccess, 'Could not encrypt/safe the password safe!'
132
+ end
133
+
134
+ # Checks if the file is accessible or create a new one
135
+ def access_safe
136
+ if !File.file? @pwfile
137
+ puts "No password safe detected, creating one at #@pwfile"
138
+ FileUtils.touch @pwfile
139
+ @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
140
+ write_safe
141
+ else
142
+ @pwhash = Encryptor.hash ask_for_password 'master password'
143
+ end
144
+ rescue
145
+ raise NoAccess, "Could not access the password safe at #@pwfile!"
146
+ end
147
+
148
+ # Adds some redundancy
149
+ def add_dummy_data(pwdata)
150
+ (5000 - pwdata.size).abs.times.map{ rand 42424242 } + # or whatever
151
+ [pwdata]
152
+ end
153
+
154
+ def remove_dummy_data(pwdata)
155
+ pwdata.last
156
+ end
157
+
158
+ # Prompts the user for a password
159
+ def ask_for_password(prompt = 'new password')
160
+ print "#{prompt}: ".capitalize
161
+ system 'stty -echo' # no more terminal output
162
+ pw_plaintext = ($stdin.gets||'').chop # gets without $stdin would mistakenly read_safe from ARGV
163
+ system 'stty echo' # restore terminal output
164
+ puts
165
+
166
+ pw_plaintext
167
+ end
168
+
169
+ class << Encryptor = Module.new
170
+ CIPHER = 'AES256'
171
+
172
+ def decrypt( data, pwhash )
173
+ crypt :decrypt, data, pwhash
174
+ end
175
+
176
+ def encrypt( data, pwhash )
177
+ crypt :encrypt, data, pwhash
178
+ end
179
+
180
+ def hash( plaintext )
181
+ OpenSSL::Digest::SHA512.new( plaintext ).digest
182
+ end
183
+
184
+ private
185
+
186
+ # Encrypts or decrypts the data with the password hash as key
187
+ # NOTE: encryption exceptions do not get caught!
188
+ def crypt( decrypt_or_encrypt, data, pwhash )
189
+ c = OpenSSL::Cipher.new CIPHER
190
+ c.send decrypt_or_encrypt.to_sym
191
+ c.key = pwhash
192
+ c.update( data ) << c.final
193
+ end
194
+ end
195
+ end
196
+
197
+ # Command line action
198
+ if standalone? # using zucker/kernel (instead of __FILE__ == $0)
199
+ if $*.empty?
200
+ action = :show
201
+ else
202
+ action = $*.shift[/^-{0,2}(.*)$/, 1].to_sym # also accept first argument, if it is prefixed with - or --
203
+ args = [*$*]
204
+ end
205
+
206
+ begin
207
+ case action
208
+ when :h, :help, :commands
209
+ puts %q{Available commands
210
+ s/show/list shows all available entry names
211
+ g/get/entry( name, seconds = 20 )
212
+ copies the password of the entry into the clipboard
213
+ d/description( names ) shows a description for the password entries
214
+ a/add/set/create( name, description = nil, password = nil )
215
+ creates or updates an entry. second parameter is a
216
+ description. third parameter can be a password, but
217
+ it's recommended to not use it and enter it, when prompted
218
+ gen/generate( name, description = nil, length=128, chars = (32..126).map(&:chr) )
219
+ creates or updates an entry, but generates a new
220
+ random password. you can customize the length and
221
+ used characters
222
+ r/remove/delete( name ) deletes an password entry
223
+ m/master changes the master password
224
+ v/version displays version
225
+ h/help/commands displays this help}
226
+ when :v, :version
227
+ puts "pws #{PasswordSafe::VERSION}\n J-_-L"
228
+ else # redirect to safe
229
+ if PasswordSafe.public_instance_methods(false).include?(
230
+ if RubyVersion.is?(1.8) then action.to_s else action end ) # using zucker/version
231
+
232
+ pws = PasswordSafe.new
233
+ pws.send action, *args
234
+ else
235
+ puts "Unknown command: #{action}. Use 'help' to get a command list!"
236
+ end
237
+ end
238
+
239
+ rescue PasswordSafe::NoAccess => e
240
+ warn e.message
241
+ end
242
+ end
243
+
244
+ # J-_-L
data/pws.gemspec ADDED
@@ -0,0 +1,44 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pws}
8
+ s.version = "0.1.0.pre"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jan Lelis"]
12
+ s.date = %q{2010-11-01}
13
+ s.default_executable = %q{pws}
14
+ s.email = %q{mail@janlelis.de}
15
+ s.executables = ["pws"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "Rakefile",
24
+ "VERSION",
25
+ "bin/pws",
26
+ "pws.gemspec"
27
+ ]
28
+ s.homepage = %q{http://rbjl.net/41}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.7}
32
+ s.summary = %q{pws is a little PasswordSafe}
33
+
34
+ if s.respond_to? :specification_version then
35
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
36
+ s.specification_version = 3
37
+
38
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
39
+ else
40
+ end
41
+ else
42
+ end
43
+ end
44
+
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pws
3
+ version: !ruby/object:Gem::Version
4
+ hash: 961915980
5
+ prerelease: true
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ - pre
11
+ version: 0.1.0.pre
12
+ platform: ruby
13
+ authors:
14
+ - Jan Lelis
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2010-11-01 00:00:00 +01:00
20
+ default_executable: pws
21
+ dependencies: []
22
+
23
+ description:
24
+ email: mail@janlelis.de
25
+ executables:
26
+ - pws
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - LICENSE
31
+ files:
32
+ - .document
33
+ - .gitignore
34
+ - LICENSE
35
+ - Rakefile
36
+ - VERSION
37
+ - bin/pws
38
+ - pws.gemspec
39
+ has_rdoc: true
40
+ homepage: http://rbjl.net/41
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ hash: 3
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">"
61
+ - !ruby/object:Gem::Version
62
+ hash: 25
63
+ segments:
64
+ - 1
65
+ - 3
66
+ - 1
67
+ version: 1.3.1
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.3.7
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: pws is a little PasswordSafe
75
+ test_files: []
76
+