checksum-tools 1.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.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'net-ssh-krb', :platform => :ruby_19
6
+ gem 'net-ssh-kerberos', :platform => :ruby_18
7
+
8
+ group :development do
9
+ gem "rcov", :platform => :mri_18
10
+ end
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ checksum-tools (1.1.0)
5
+ net-sftp
6
+ net-ssh
7
+ progressbar
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.2.4)
13
+ ffi (1.9.0)
14
+ gssapi (1.1.2)
15
+ ffi (>= 1.0.1)
16
+ json (1.8.0)
17
+ net-sftp (2.1.2)
18
+ net-ssh (>= 2.6.5)
19
+ net-ssh (2.7.0)
20
+ net-ssh-kerberos (0.2.7)
21
+ net-ssh (>= 2.0)
22
+ net-ssh-krb (0.3.0)
23
+ gssapi (~> 1.1.2)
24
+ net-ssh (>= 2.0)
25
+ progressbar (0.21.0)
26
+ rake (10.1.0)
27
+ rcov (1.0.0)
28
+ rdoc (4.0.1)
29
+ json (~> 1.4)
30
+ rspec (2.14.1)
31
+ rspec-core (~> 2.14.0)
32
+ rspec-expectations (~> 2.14.0)
33
+ rspec-mocks (~> 2.14.0)
34
+ rspec-core (2.14.5)
35
+ rspec-expectations (2.14.3)
36
+ diff-lcs (>= 1.1.3, < 2.0)
37
+ rspec-mocks (2.14.3)
38
+ yard (0.8.7.2)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ checksum-tools!
45
+ net-ssh-kerberos
46
+ net-ssh-krb
47
+ rake (>= 0.8.7)
48
+ rcov
49
+ rdoc
50
+ rspec
51
+ yard
@@ -0,0 +1,109 @@
1
+ = Name
2
+
3
+ checksum-tools --- Generate or verify checksums for a set of files
4
+
5
+ = Setup
6
+
7
+ == Requirements
8
+
9
+ * Ruby 1.8.7
10
+ * Rubygems 1.3.6 or greater
11
+
12
+ == Installation
13
+
14
+ gem install checksum-tools
15
+
16
+ = Synopsis
17
+
18
+ Usage: checksum-tools [options] [path]
19
+ -a, --action ACTION Specify action to take
20
+ generate|verify
21
+ -c, --config FILE Load configuration from FILE
22
+ -d, --digest DIGEST Generate checksums of type DIGEST
23
+ -e, --extension EXT File extension for digest files
24
+ -f, --filemask MASK Include files matching MASK
25
+ -n, --no-action Dry run; don't execute
26
+ -o, --overwrite Overwrite existing digest files
27
+ -q, --quiet Hide the progress bar
28
+ -r, --recursive Recurse into subdirectories
29
+ -x, --exclude MASK Exclude files matching MASK
30
+ -D, --digest-types Show digest types
31
+ -v, --version Print version and exit
32
+ -h, --help Show this help message
33
+
34
+ = Description
35
+
36
+ checksum-tools is a dual-purpose tool to generate or verify checksums of various types for a set of files in a given directory.
37
+
38
+ = Options
39
+
40
+ <tt>-a, --action ACTION</tt>
41
+
42
+ Valid choices are <tt>generate</tt> and <tt>verify</tt>. In <tt>generate</tt> mode, a digest file is created for each content file processed. In <tt>verify</tt> mode, the digests in digest files are verified against the content files and PASS/FAIL information written to standard output. Default: <tt>verify</tt>.
43
+
44
+ <tt>-c, --config FILE</tt>
45
+
46
+ Load default configuration values from <tt>FILE</tt>. Default: a file called <tt>.checksum-tools</tt> in the user's home directory. Options specified on the command line will override any defaults in the configuration file.
47
+
48
+ <tt>-d, --digest DIGEST</tt>
49
+
50
+ In <tt>generate</tt> mode, specifies the type of digest to generate. Valid options depend on the Ruby libraries available on the system, but usually include <tt>md5</tt>, <tt>sha1</tt>, <tt>sha256</tt>, <tt>sha384</tt>, and <tt>sha512</tt>. Default: <tt>md5</tt>. Including this option multiple times causes multiple digests to be generated for each file.
51
+
52
+ <tt>-D, --digest-types</tt>
53
+
54
+ Display the available digest types for the specified target directory/path/system.
55
+
56
+ <tt>-e, --extension EXT</tt>
57
+
58
+ The file extension to be appended to the names of content files when generating digest files. Default: <tt>.digest</tt>.
59
+
60
+ <tt>-f, --filemask MASK</tt>
61
+
62
+ Generate or verify checksums for files matching <tt>MASK</tt>. Include this option multiple times to match multiple filemasks. Default: <tt>*</tt> (all files)
63
+
64
+ <tt>-n, --no-action</tt>
65
+
66
+ Dry run — display configuration options and exit.
67
+
68
+ <tt>-o, --overwrite</tt>
69
+
70
+ Overwrite existing digest files with newly-generated ones.
71
+
72
+ <tt>-q, --quiet</tt>
73
+
74
+ Do not display a progress bar for each file generated.
75
+
76
+ <tt>-r, --recursive</tt>
77
+
78
+ Recurse into subdirectories.
79
+
80
+ <tt>-v, --version</tt>
81
+
82
+ Print version number and exit.
83
+
84
+ <tt>-x, --exclude MASK</tt>
85
+
86
+ Exclude files matching <tt>MASK</tt> from the operation. Can be used to skip a certain subset of files otherwise included using the <tt>--filemask</tt> option.
87
+
88
+ <tt>-h, --help</tt>
89
+
90
+ Display a simple help message and exit.
91
+
92
+ == Releases
93
+ - <b>0.6.6</b> Initial release
94
+ - <b>0.7.0</b> Added ability to verify against digest files created by other tools
95
+ - <b>0.7.1</b> Added -v / --version switch
96
+ - <b>0.7.2</b> Fixed shift error when evaluating .digest files
97
+ - <b>0.9.0</b> Added remote openssl invocation capability to generate digests on non-Ruby systems
98
+ - <b>0.9.1</b> Slight refactoring of bin/checksum-tools; removed debugging code
99
+ - <b>0.9.2</b> Fix gem deployment problem with bundler
100
+ - <b>0.9.3</b> Hotfix to revert SFTP/SSH object changes in 0.9.2
101
+ - <b>0.9.4</b> [HYDRALABWARE-59] Checksum-tools anomalies
102
+ - <b>0.9.5</b> Minor bug fixes and spec tests
103
+ - <b>1.0.0</b> Add --openssl command line parameter for self-configuration of remotes
104
+ - <b>1.0.1</b> Hotfix for broken spec test
105
+ - <b>1.0.3</b> Hotfix for remote paths with spaces in them
106
+ - <b>1.0.4</b> Hotfix for remote paths with singles quotes in them
107
+ - <b>1.0.5</b> Ensure remote commands are always invoked through bash
108
+ - <b>1.0.6</b> Fix remote digest type introspection bug introduced in 1.0.5
109
+ - <b>1.0.7</b> Compensate for differences between OpenSSL 0.9.8 and OpenSSL 1.0.0
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'bundler'
4
+ require 'bundler/gem_tasks'
5
+
6
+ Dir.glob('lib/tasks/*.rake').each { |r| import r }
7
+
8
+ begin
9
+ Bundler.setup(:default, :development)
10
+ rescue Bundler::BundlerError => e
11
+ $stderr.puts e.message
12
+ $stderr.puts "Run `bundle install` to install missing gems"
13
+ exit e.status_code
14
+ end
15
+
16
+ require 'rspec/core/rake_task'
17
+ RSpec::Core::RakeTask.new(:spec)
18
+
19
+ RSpec::Core::RakeTask.new(:features) do |spec|
20
+ spec.libs << 'lib' << 'spec'
21
+ spec.spec_files = FileList['spec/features/**/*_spec.rb']
22
+ end
23
+
24
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
25
+ spec.libs << 'lib' << 'spec'
26
+ spec.pattern = 'spec/unit_tests/**/*.rb'
27
+ spec.rcov = true
28
+ spec.rcov_opts = %w{--exclude spec\/*,gems\/*,ruby\/* --aggregate coverage.data}
29
+ end
30
+
31
+
32
+ task :clean do
33
+ puts 'Cleaning old coverage.data'
34
+ FileUtils.rm('coverage.data') if(File.exists? 'coverage.data')
35
+ end
36
+
37
+ task :default => [:spec]
38
+
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'checksum-tools'
6
+ rescue LoadError
7
+ require 'bundler/setup'
8
+ require 'checksum-tools'
9
+ end
10
+ require 'optparse'
11
+ require 'progressbar'
12
+ require 'yaml'
13
+
14
+ DEFAULT_OPTIONS = {
15
+ :action => 'verify',
16
+ :digests => [],
17
+ :filemasks => [],
18
+ :progress => true,
19
+ :exclude => [],
20
+ :extension => '.digest',
21
+ :openssl => nil,
22
+ :overwrite => false,
23
+ :recursive => false
24
+ }
25
+
26
+ @pbar = nil
27
+ def progress(filename, size, pos)
28
+ unless size.nil?
29
+ if pos == -1
30
+ @pbar.finish unless @pbar.nil?
31
+ @pbar = nil
32
+ else
33
+ if @pbar.nil?
34
+ @pbar = ProgressBar.new(File.basename(filename), size)
35
+ end
36
+ @pbar.set(pos)
37
+ end
38
+ end
39
+ end
40
+
41
+ def generate(tool, path, options)
42
+ tool.create_digest_files(path,options[:filemasks]) do |filename, size, pos|
43
+ progress(filename, size, pos) if options[:progress]
44
+ end
45
+ end
46
+
47
+ def verify(tool, path, options)
48
+ tool.verify_digest_files(path,options[:filemasks]) do |filename, size, pos, result|
49
+ if result.nil?
50
+ progress(filename, size, pos) if options[:progress]
51
+ else
52
+ progress(filename, -1, -1) if options[:progress]
53
+ result.delete_if { |k,v| v == true }
54
+ if result.length == 0
55
+ print "PASS"
56
+ else
57
+ print "FAIL [#{result.keys.join(',')}]"
58
+ end
59
+ puts " #{filename}"
60
+ end
61
+ end
62
+ end
63
+
64
+ VALID_ACTIONS = ['generate','verify','console']
65
+
66
+ config_file = File.join(ENV['HOME'], '.checksum-tools')
67
+ config_file_options = begin
68
+ YAML.load(File.read(config_file))
69
+ rescue
70
+ {}
71
+ end
72
+
73
+ cmdline_options = {}
74
+ dry_run = false
75
+
76
+ optparse = OptionParser.new do |opts|
77
+ opts.banner = "Usage: #{File.basename($0)} [options] [path]"
78
+
79
+ opts.on('-a', '--action ACTION', VALID_ACTIONS, "Specify action to take", "#{VALID_ACTIONS.join('|')}") do |action|
80
+ cmdline_options[:action] = action
81
+ end
82
+
83
+ opts.on('-c', '--config FILE', "Load configuration from FILE") do |filename|
84
+ config_file = filename
85
+ config_file_options = YAML.load(File.read(config_file))
86
+ end
87
+
88
+ opts.on('-d', '--digest DIGEST', "Generate checksums of type DIGEST") do |digest|
89
+ (cmdline_options[:digests] ||= []) << digest.to_sym
90
+ end
91
+
92
+ opts.on('-e', '--extension EXT', "File extension for digest files") do |ext|
93
+ cmdline_options[:extension] = ext
94
+ end
95
+
96
+ opts.on('-f', '--filemask MASK', "Include files matching MASK") do |mask|
97
+ (cmdline_options[:filemasks] ||= []) << mask
98
+ end
99
+
100
+ opts.on('-n', '--no-action', "Dry run; don't execute") do
101
+ dry_run = true
102
+ end
103
+
104
+ opts.on('-o', '--overwrite', "Overwrite existing digest files") do
105
+ cmdline_options[:overwrite] = true
106
+ end
107
+
108
+ opts.on('-q', '--quiet', "Hide the progress bar") do
109
+ cmdline_options[:progress] = false
110
+ end
111
+
112
+ opts.on('-r', '--recursive', "Recurse into subdirectories") do
113
+ cmdline_options[:recursive] = true
114
+ end
115
+
116
+ opts.on('-s', '--openssl PATH', "Specify location of openssl on remote server") do |arg|
117
+ cmdline_options[:openssl] = arg
118
+ end
119
+
120
+ opts.on('-x', '--exclude MASK', "Exclude files matching MASK") do |mask|
121
+ cmdline_options[:exclude] << mask
122
+ end
123
+
124
+ opts.on_tail('-D', '--digest-types', "Show digest types") do
125
+ path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
126
+ tool = Checksum::Tools.new(path_info, cmdline_options)
127
+ puts "Available digest types for #{tool.host || 'local system'}:"
128
+ puts tool.digests.join(' ')
129
+ exit
130
+ end
131
+
132
+ opts.on_tail('-v', '--version', "Print version and exit") do
133
+ puts "checksum-tools #{Checksum::Tools::VERSION}"
134
+ exit
135
+ end
136
+
137
+ opts.on_tail('-h', '--help', "Show this help message") do
138
+ puts opts
139
+ exit
140
+ end
141
+ end
142
+
143
+ optparse.parse!
144
+
145
+ options = DEFAULT_OPTIONS.merge(config_file_options).merge(cmdline_options)
146
+ options[:filemasks] << '*' if options[:filemasks].length == 0
147
+ options[:digests] << :md5 if options[:digests].length == 0
148
+ options[:digests].uniq!
149
+ options[:filemasks].uniq!
150
+ options[:exclude].uniq!
151
+
152
+ if options[:extension] !~ /[A-Za-z0-9]/
153
+ puts optparse
154
+ exit
155
+ end
156
+
157
+ if dry_run
158
+ puts "Defaults loaded from #{config_file}"
159
+ puts YAML.dump(options)
160
+ exit
161
+ end
162
+
163
+ args = options[:digests].dup
164
+ args << options.dup.reject { |k,v| not [:exclude,:extension,:openssl,:overwrite,:recursive].include?(k) }
165
+ path_info = Checksum::Tools.parse_path(ARGV.pop || '.')
166
+ tool = Checksum::Tools.new(path_info, *args)
167
+
168
+ case options[:action]
169
+ when 'generate' then generate(tool, path_info[:dir], options)
170
+ when 'verify' then verify(tool, path_info[:dir], options)
171
+ when 'console' then require 'ripl'; Ripl.start :binding => binding
172
+ end
@@ -0,0 +1,180 @@
1
+ module Checksum
2
+ module Tools
3
+ VERSION = "1.1.0"
4
+
5
+ CHUNK_SIZE = 1048576 # 1M blocks
6
+ DEFAULT_OPTS = { :overwrite => false, :recursive => false, :exclude => [], :extension => '.digest' }
7
+
8
+ autoload :Local, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'local')
9
+ autoload :Remote, File.join(File.dirname(__FILE__), File.basename(__FILE__, '.rb'), 'remote')
10
+
11
+ class Exception < ::Exception; end
12
+ class ConfigurationError < Exception; end
13
+
14
+ class << self
15
+ def new(path_info, *args)
16
+ remote = path_info[:remote]
17
+ if remote.nil? or remote[:host].nil?
18
+ Local.new(*args)
19
+ else
20
+ Remote.new(remote[:host],remote[:user],*args)
21
+ end
22
+ end
23
+
24
+ def parse_path(path)
25
+ user,host,dir = path.to_s.scan(/^(?:(.+)@)?(?:(.+):)?(?:(.+))?$/).flatten
26
+ dir ||= '.'
27
+ result = { :remote => { :user => user, :host => host }, :dir => dir }
28
+ return result
29
+ end
30
+ end
31
+
32
+ class Base
33
+ attr_reader :host
34
+
35
+ def initialize(*args)
36
+ @opts = DEFAULT_OPTS
37
+ if args.last.is_a?(Hash)
38
+ @opts.merge!(args.pop)
39
+ end
40
+ @opts[:exclude] << digest_filename("*")
41
+ @opts[:exclude].uniq!
42
+ @digest_types = args
43
+ end
44
+
45
+ def digest_filename(filename)
46
+ "#{filename}.#{opts[:extension].sub(/^\.+/,'')}"
47
+ end
48
+
49
+ def create_digest_files(base_dir, file_masks, &block)
50
+ process_files(base_dir, file_masks) do |filename|
51
+ yield(filename, -1, -1) if block_given?
52
+ create_digest_file(filename, &block)
53
+ end
54
+ end
55
+
56
+ def create_digest_file(filename, &block)
57
+ digest_file = digest_filename(filename)
58
+ if File.expand_path(filename) == File.expand_path(digest_file)
59
+ raise ArgumentError, "Digest file #{digest_file} will clobber content file #{filename}!"
60
+ end
61
+ if opts[:overwrite] or not file_exists?(digest_file)
62
+ result = digest_file(filename, &block)
63
+ file_open(digest_file,'w') do |cksum|
64
+ result.each_pair do |type,hexdigest|
65
+ cksum.puts("#{type.to_s.upcase}(#{File.basename(filename)})= #{hexdigest}")
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def digest_files(base_dir, file_masks, &block)
72
+ process_files(base_dir, file_masks) do |filename|
73
+ yield(filename, -1, -1) if block_given?
74
+ digest_file(filename, &block)
75
+ end
76
+ end
77
+
78
+ def verify_digest_files(base_dir, file_masks, &block)
79
+ process_files(base_dir, file_masks) do |filename|
80
+ yield(filename, -1, -1) if block_given?
81
+ result = verify_digest_file(filename, &block)
82
+ yield(filename, -1, 0, result)
83
+ result
84
+ end
85
+ end
86
+
87
+ def verify_digest_file(filename, &block)
88
+ digest_file = digest_filename(filename)
89
+ unless file_exists?(digest_file)
90
+ return { :digest_file => false }
91
+ end
92
+
93
+ hashes = {}
94
+ digest_data = file_read(digest_file)
95
+
96
+ ext_type = File.extname(digest_file)[1..-1].downcase.to_sym
97
+ # Check to see if the digest file extension implies a specific digest type (e.g., .md5)
98
+ if digests.include?(ext_type)
99
+ # Find a hex value of the correct length in the digest file
100
+ len = self.digest_length(ext_type)
101
+ digest = digest_data.scan(/\b((?:[0-9a-f]{4})+)\b/im).flatten.find { |d| d.length == len }
102
+ hashes[ext_type] = digest
103
+ else
104
+ digest_data = digest_data.split(/\n/)
105
+ hashes = digest_data.inject({}) do |collector,sum|
106
+ (hash,hashed_fname,digest) = sum.scan(/(.+)\((.+)\)= (.+)/).flatten
107
+ unless File.basename(hashed_fname) == File.basename(filename)
108
+ warn "WARNING: Filename mismatch in #{digest_file}: #{File.basename(hashed_fname)}"
109
+ end
110
+ collector[hash.downcase.to_sym] = digest
111
+ collector
112
+ end
113
+ end
114
+ verify_file(filename, hashes, &block)
115
+ end
116
+
117
+ def verify_file(filename, hashes, &block)
118
+ with_types(hashes.keys) do
119
+ actual = digest_file(filename, &block)
120
+ result = {}
121
+ actual.each_pair do |hash,digest|
122
+ result[hash] = hashes[hash].downcase == digest.downcase
123
+ end
124
+ result
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def glob_to_re(glob)
131
+ replacements = [
132
+ [/\./,'##DOT##'],[/\\\*/,'##STAR##'],[/\\\?/,'##QUEST##'],
133
+ [/\*/,'.*'],[/\?/,'.'],
134
+ [/##STAR##/,'\*'],[/##QUEST##/,'\?'],[/##DOT##/,'\.']
135
+ ]
136
+ result = glob.dup
137
+ replacements.each { |args| result.gsub!(*args) }
138
+ Regexp.new(result)
139
+ end
140
+
141
+ def file_read(filename)
142
+ if file_exists?(filename)
143
+ result = ''
144
+ file_open(filename) { |io| result = io.read }
145
+ return result
146
+ else
147
+ raise Errno::ENOENT, filename
148
+ end
149
+ end
150
+
151
+ def process_files(base_dir, include_masks = ["*"])
152
+ unless block_given?
153
+ raise ArgumentError, "no block given"
154
+ end
155
+
156
+ excludes = Array(opts[:exclude]).collect { |ex| glob_to_re(ex) }
157
+ targets = file_list(base_dir, *include_masks).reject { |f| excludes.any? { |re| f.match(re) } }
158
+ targets.sort!
159
+ targets.uniq!
160
+
161
+ result = {}
162
+ targets.each do |filename|
163
+ result[filename] = yield(filename)
164
+ end
165
+ return result
166
+ end
167
+
168
+ def with_types(new_types)
169
+ old_types = @digest_types
170
+ @digest_types = new_types
171
+ begin
172
+ return(yield)
173
+ ensure
174
+ @digest_types = old_types
175
+ end
176
+ end
177
+ end
178
+
179
+ end
180
+ end
@@ -0,0 +1,122 @@
1
+ module Checksum::Tools
2
+
3
+ class Local < Base
4
+
5
+ class << self
6
+ @@digests = {}
7
+
8
+ def register_digest(key, file, &block)
9
+ if @@digests.has_key?(key)
10
+ return false
11
+ else
12
+ begin
13
+ require file
14
+ @@digests[key] = block
15
+ return key
16
+ rescue LoadError
17
+ return false
18
+ end
19
+ end
20
+ end
21
+
22
+ def digest_for(key)
23
+ handler = @@digests[key]
24
+ if handler.nil?
25
+ raise ArgumentError, "undefined digest class: #{key.to_s}"
26
+ end
27
+ return handler.call()
28
+ end
29
+
30
+ def digests
31
+ @@digests.keys
32
+ end
33
+ end
34
+
35
+ attr :digest_types
36
+ attr :opts
37
+
38
+ def initialize(*args)
39
+ super(*args)
40
+ @digest_types.each { |type| self.class.digest_for(type) }
41
+ end
42
+
43
+ def digests
44
+ self.class.digests
45
+ end
46
+
47
+ def digest_length(type)
48
+ self.class.digest_for(type).hexdigest.length
49
+ end
50
+
51
+ def digest_file(filename)
52
+ if File.exists?(filename)
53
+ size = file_size(filename)
54
+ block = block_given? ? lambda { |pos| yield(filename, size, pos) } : nil
55
+ File.open(filename, 'r') do |io|
56
+ yield(filename, size, 0) if block_given?
57
+ return digest_stream(io, &block)
58
+ end
59
+ else
60
+ raise Errno::ENOENT, filename
61
+ end
62
+ end
63
+
64
+ def digest_stream(io)
65
+ output = digest_types.inject({}) { |collector,key| collector[key] = self.class.digest_for(key); collector }
66
+ io.rewind
67
+ while chunk = io.read(CHUNK_SIZE)
68
+ output.values.each do |digest|
69
+ digest << chunk
70
+ end
71
+ if block_given?
72
+ yield(io.pos)
73
+ end
74
+ end
75
+ output.each_pair do |type,digest|
76
+ output[type] = digest.hexdigest
77
+ end
78
+ return output
79
+ end
80
+
81
+ def verify_stream(io, hashes, &block)
82
+ with_types(hashes.keys) do
83
+ actual = digest_stream(io, &block)
84
+ result = {}
85
+ actual.each_pair do |hash,digest|
86
+ result[hash] = hashes[hash] == digest
87
+ end
88
+ result
89
+ end
90
+ end
91
+
92
+ protected
93
+
94
+ def file_list(base_dir, *file_masks)
95
+ path = [base_dir, '*']
96
+ path.insert(1,'**') if opts[:recursive]
97
+ result = Dir[File.join(*path)]
98
+ result.reject! { |f| File.directory?(f) or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f)) }) }
99
+ result
100
+ end
101
+
102
+ def file_open(*args, &block)
103
+ File.open(*args, &block)
104
+ end
105
+
106
+ def file_exists?(filename)
107
+ File.exists?(filename)
108
+ end
109
+
110
+ def file_size(filename)
111
+ File.size(filename)
112
+ end
113
+
114
+ end
115
+
116
+ end
117
+
118
+ Checksum::Tools::Local.register_digest(:md5, 'digest/md5') { Digest::MD5.new }
119
+ Checksum::Tools::Local.register_digest(:sha1, 'digest/sha1') { Digest::SHA1.new }
120
+ Checksum::Tools::Local.register_digest(:sha256, 'digest/sha2') { Digest::SHA2.new(256) }
121
+ Checksum::Tools::Local.register_digest(:sha384, 'digest/sha2') { Digest::SHA2.new(384) }
122
+ Checksum::Tools::Local.register_digest(:sha512, 'digest/sha2') { Digest::SHA2.new(512) }
@@ -0,0 +1,148 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp'
3
+
4
+ begin
5
+ require 'net/ssh/kerberos'
6
+ rescue LoadError
7
+ raise LoadError.new "Include 'net-ssh-kerberos' (ruby 1.8) or 'net-ssh-krb' (ruby 1.9) in your Gemfile"
8
+ end
9
+
10
+ module Checksum::Tools
11
+
12
+ class Remote < Base
13
+
14
+ attr :digest_types
15
+ attr :opts
16
+
17
+ def initialize(host, user, *args)
18
+ super(*args)
19
+ @host = host
20
+ @user = user
21
+ @digest_length_cache = {}
22
+ unless (ssl = opts.delete(:openssl)).nil?
23
+ begin
24
+ remote_properties[:openssl] = ssl
25
+ rescue ConfigurationError
26
+ @remote_properties = { :openssl => ssl }
27
+ end
28
+ write_remote_properties
29
+ end
30
+ end
31
+
32
+ def openssl
33
+ remote_properties[:openssl]
34
+ end
35
+
36
+ def sftp
37
+ @sftp ||= begin
38
+ auth_methods = %w(gssapi-with-mic publickey hostbased) if defined? Net::SSH::Kerberos
39
+ auth_methods ||= %w(publickey hostbased)
40
+ Net::SFTP.start(@host, @user, :auth_methods => auth_methods)
41
+ end
42
+ end
43
+
44
+ def ssh
45
+ result = sftp.session
46
+ if block_given?
47
+ channel = result.open_channel do |ch|
48
+ yield(ch)
49
+ end
50
+ channel.wait
51
+ end
52
+ result
53
+ end
54
+
55
+ def exec!(cmd)
56
+ result = ''
57
+ ssh do |ch|
58
+ ch.exec("bash -l") do |ch2, success|
59
+ ch2.on_data { |c,data| result += data }
60
+ ch2.send_data "#{cmd}\n"
61
+ ch2.send_data "exit\n"
62
+ end
63
+ end
64
+ result.chomp
65
+ end
66
+
67
+ def digests
68
+ resp = ''
69
+ resp = exec! "#{openssl} dgst -h 2>&1"
70
+ resp.scan(/-(.+?)\s+to use the .+ message digest algorithm/).flatten.collect { |d| d.to_sym }
71
+ end
72
+
73
+ def digest_length(type)
74
+ if @digest_length_cache[type].nil?
75
+ resp = exec! "echo - | #{openssl} dgst -#{type}"
76
+ @digest_length_cache[type] = resp.split(/\s/).last.chomp.length
77
+ end
78
+ @digest_length_cache[type]
79
+ end
80
+
81
+ def digest_file(filename)
82
+ if file_exists?(filename)
83
+ size = file_size(filename)
84
+ yield(filename, size, 0) if block_given?
85
+ output = {}
86
+ digest_types.each { |key|
87
+ resp = exec! "#{openssl} dgst -#{key} $'#{filename.gsub(/[']/,'\\\\\'')}'"
88
+ output[key] = resp.split(/\= /).last
89
+ }
90
+ yield(filename, size, size) if block_given?
91
+ return output
92
+ else
93
+ raise Errno::ENOENT, filename
94
+ end
95
+ end
96
+
97
+ protected
98
+
99
+ def file_list(base_dir, *file_masks)
100
+ path = ['*']
101
+ path.unshift('**') if opts[:recursive]
102
+ result = sftp.dir[base_dir, File.join(*path)]
103
+ result.reject! { |f| f.directory? or (not file_masks.any? { |m| File.fnmatch?(m,File.basename(f.name)) }) }
104
+ result.collect { |f| File.expand_path(File.join(base_dir, f.name)) }
105
+ end
106
+
107
+ def file_open(*args, &block)
108
+ sftp.file.open(*args, &block)
109
+ end
110
+
111
+ def file_exists?(filename)
112
+ sftp.stat!(filename)
113
+ return true
114
+ rescue Net::SFTP::StatusException
115
+ return false
116
+ end
117
+
118
+ def file_size(filename)
119
+ if file_exists?(filename)
120
+ sftp.stat!(filename).size
121
+ else
122
+ raise Errno::ENOENT, filename
123
+ end
124
+ end
125
+
126
+ def remote_properties
127
+ if @remote_properties.nil?
128
+ home_dir = exec!('echo $HOME')
129
+ settings_file = File.join(home_dir,".checksum-tools-system")
130
+ if file_exists?(settings_file)
131
+ @remote_properties = YAML.load(file_read(settings_file))
132
+ else
133
+ raise ConfigurationError, "Checksum Tools not configured for #{@user}@#{host}. Please use the --openssl parameter to specify the location of the remote openssl binary."
134
+ end
135
+ end
136
+ @remote_properties
137
+ end
138
+
139
+ def write_remote_properties
140
+ home_dir = exec!('echo $HOME')
141
+ settings_file = File.join(home_dir,".checksum-tools-system")
142
+ file_open(settings_file, 'w') { |io| YAML.dump(remote_properties, io) }
143
+ remote_properties
144
+ end
145
+
146
+ end
147
+
148
+ end
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: checksum-tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Michael Klein
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: progressbar
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: net-ssh
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: net-sftp
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.8.7
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.8.7
78
+ - !ruby/object:Gem::Dependency
79
+ name: rdoc
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: rspec
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: yard
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Contains classes and executable files to generate and verify checksums
127
+ email:
128
+ - mbklein@stanford.edu
129
+ executables:
130
+ - checksum-tools
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - lib/checksum-tools/local.rb
135
+ - lib/checksum-tools/remote.rb
136
+ - lib/checksum-tools.rb
137
+ - bin/checksum-tools
138
+ - Gemfile
139
+ - Gemfile.lock
140
+ - Rakefile
141
+ - README.rdoc
142
+ homepage:
143
+ licenses: []
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ segments:
155
+ - 0
156
+ hash: -3039575937498161987
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ none: false
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: 1.3.6
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 1.8.23
166
+ signing_key:
167
+ specification_version: 3
168
+ summary: Checksum creation and verification tools
169
+ test_files: []
170
+ has_rdoc: