sambal 0.1.9 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +4 -3
- data/lib/sambal/client.rb +61 -16
- data/lib/sambal/test_server.rb +3 -3
- data/lib/sambal/version.rb +1 -1
- data/spec/sambal/client_spec.rb +52 -0
- metadata +6 -13
- data/.gitignore +0 -19
- data/Gemfile +0 -6
- data/Rakefile +0 -2
- data/Spookfile +0 -11
- data/circle.yml +0 -4
- data/sambal.gemspec +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9fc6edc1f7ee1557e9bb47fa9af736415cfa7c43fbe9a0869a81cc63f738d7ab
|
4
|
+
data.tar.gz: 7664aaf2c58023cbe3f7e5eb3d486a6340ce650def28ca32a311024611bdece5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0901a04a4fe2f864ac3e457cae9d0a238fcde13213745644d49127b2565d32830f4b90b768c864fec1381bd8665152fd651c8a5a7016c70877f8b7eef81bec8e'
|
7
|
+
data.tar.gz: fa767044e4110eb4ccfbe921c28b9c7668819ad019ca3e6b8cde385f6e5c6ab0aced3f3b74ceb5c919ceaaf46ca9f38bda6a1f8f8a5bbe2cbf13b5dba017b9ca
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://ci.insane.se/job/sambal/job/master/)
|
2
2
|
|
3
3
|
# Sambal
|
4
4
|
|
@@ -27,14 +27,15 @@ On a mac this can be installed through homebrew https://github.com/mxcl/homebrew
|
|
27
27
|
|
28
28
|
brew install samba
|
29
29
|
|
30
|
-
On the Mac it can probably also be installed both through Fink and MacPorts.
|
31
|
-
|
32
30
|
On Linux (Ubuntu) it's as easy as:
|
33
31
|
|
34
32
|
apt-get install smbclient
|
35
33
|
|
36
34
|
It should be available in a similar way on all major Linux distributions.
|
37
35
|
|
36
|
+
If you happen to be running [NixOS](https://nixos.org/) or at least the [Nix package manager](https://nixos.org/nix/download.html) you could just
|
37
|
+
use the `default.nix` file in this repo. That should also set you up for running the tests (which require samba).
|
38
|
+
|
38
39
|
## Usage
|
39
40
|
|
40
41
|
client = Sambal::Client.new(domain: 'WORKGROUP', host: '127.0.0.1', share: '', user: 'guest', password: '--no-pass', port: 445)
|
data/lib/sambal/client.rb
CHANGED
@@ -18,7 +18,8 @@ module Sambal
|
|
18
18
|
password: false,
|
19
19
|
port: 445,
|
20
20
|
timeout: 10,
|
21
|
-
columns: 80
|
21
|
+
columns: 80,
|
22
|
+
smbclient_command: 'smbclient'
|
22
23
|
}
|
23
24
|
|
24
25
|
options = default_options.merge(user_options)
|
@@ -31,18 +32,21 @@ module Sambal
|
|
31
32
|
options = parsed_options(user_options)
|
32
33
|
@timeout = options[:timeout].to_i
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
password =
|
36
|
+
if options[:authfile]
|
37
|
+
['--authentication-file', options[:authfile]]
|
38
|
+
elsif options[:password]
|
39
|
+
[options[:password]]
|
40
|
+
else
|
41
|
+
['--no-pass']
|
42
|
+
end
|
43
|
+
command = ['env', "COLUMNS=#{options[:columns]}", options[:smbclient_command], "//#{options[:host]}/#{options[:share]}", password, option_flags(options)].flatten
|
39
44
|
|
40
|
-
@output, @input, @pid = PTY.spawn(command
|
45
|
+
@output, @input, @pid = PTY.spawn(command[0], *command[1..-1])
|
41
46
|
|
42
47
|
res = @output.expect(/(.*\n)?smb:.*\\>/, @timeout)[0] rescue nil
|
43
48
|
@connected = case res
|
44
49
|
when nil
|
45
|
-
$stderr.puts "Failed to connect"
|
46
50
|
false
|
47
51
|
when /^put/
|
48
52
|
res['putting'].nil? ? false : true
|
@@ -58,10 +62,10 @@ module Sambal
|
|
58
62
|
|
59
63
|
unless @connected
|
60
64
|
close if @pid
|
61
|
-
|
65
|
+
raise 'Failed to connect'
|
62
66
|
end
|
63
|
-
rescue
|
64
|
-
raise RuntimeError
|
67
|
+
rescue => e
|
68
|
+
raise RuntimeError, "Unknown Process Failed!! (#{$!.to_s}): #{e.message.inspect}\n"+e.backtrace.join("\n")
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
@@ -101,7 +105,7 @@ module Sambal
|
|
101
105
|
|
102
106
|
def cd(dir)
|
103
107
|
response = ask("cd \"#{dir}\"")
|
104
|
-
if response.split("\r\n").join('') =~ /
|
108
|
+
if response.split("\r\n").join('') =~ /NT_STATUS_OBJECT_(NAME|PATH)_NOT_FOUND/
|
105
109
|
Response.new(response, false)
|
106
110
|
else
|
107
111
|
Response.new(response, true)
|
@@ -123,6 +127,17 @@ module Sambal
|
|
123
127
|
end
|
124
128
|
end
|
125
129
|
|
130
|
+
def rename(old_filename, new_filename)
|
131
|
+
response = ask_wrapped 'rename', [old_filename, new_filename]
|
132
|
+
if response =~ /renaming\sfile/ # "renaming" reponse only exist if has error
|
133
|
+
Response.new(response, false)
|
134
|
+
else
|
135
|
+
Response.new(response, true)
|
136
|
+
end
|
137
|
+
rescue InternalError => e
|
138
|
+
Response.new(e.message, false)
|
139
|
+
end
|
140
|
+
|
126
141
|
def put(file, destination)
|
127
142
|
response = ask_wrapped 'put', [file, destination]
|
128
143
|
if response =~ /^putting\sfile.*$/
|
@@ -238,11 +253,17 @@ module Sambal
|
|
238
253
|
end
|
239
254
|
|
240
255
|
def ask(cmd)
|
241
|
-
@input.
|
242
|
-
response =
|
256
|
+
@input.print("#{cmd}\n")
|
257
|
+
response = begin
|
258
|
+
@output.expect(/^smb:.*\\>/,@timeout)[0]
|
259
|
+
rescue => e
|
260
|
+
$stderr.puts e
|
261
|
+
nil
|
262
|
+
end
|
263
|
+
|
243
264
|
if response.nil?
|
244
265
|
$stderr.puts "Failed to do #{cmd}"
|
245
|
-
raise
|
266
|
+
raise "Failed to do #{cmd}"
|
246
267
|
else
|
247
268
|
response
|
248
269
|
end
|
@@ -252,9 +273,13 @@ module Sambal
|
|
252
273
|
ask wrap_filenames(cmd,filenames)
|
253
274
|
end
|
254
275
|
|
276
|
+
def sanitize_filename(filename)
|
277
|
+
filename.to_s.gsub(/[[:^print:]"]/,'')
|
278
|
+
end
|
279
|
+
|
255
280
|
def wrap_filenames(cmd,filenames)
|
256
281
|
filenames = [filenames] unless filenames.kind_of?(Array)
|
257
|
-
filenames.map!{ |filename| "\"#{filename}\"" }
|
282
|
+
filenames.map!{ |filename| "\"#{sanitize_filename(filename)}\"" }
|
258
283
|
[cmd,filenames].flatten.join(' ')
|
259
284
|
end
|
260
285
|
|
@@ -286,5 +311,25 @@ module Sambal
|
|
286
311
|
end
|
287
312
|
Hash[listing.sort]
|
288
313
|
end
|
314
|
+
|
315
|
+
private
|
316
|
+
|
317
|
+
def option_flags(options)
|
318
|
+
flags = []
|
319
|
+
flags += ['--workgroup', options[:domain]] if options[:domain] && !options[:authfile]
|
320
|
+
flags += ['--user', options[:user]] if options[:user] && !options[:authfile]
|
321
|
+
flags += ['--ip-address', options[:ip_address]] if options[:ip_address]
|
322
|
+
flags += ['--send-buffer', options[:buffer_size]] if options[:buffer_size]
|
323
|
+
flags += ['--debuglevel', options[:debug_level]] if options[:debug_level]
|
324
|
+
flags += ['--encrypt'] if options[:encrypt]
|
325
|
+
flags += ['--max-protocol', options[:max_protocol]] if options[:max_protocol]
|
326
|
+
flags += ['--use-ccache'] if options[:use_ccache]
|
327
|
+
flags += ['--socket-options', options[:socket_options]] if options[:socket_options]
|
328
|
+
flags += ['--port', options[:port]] if options[:port]
|
329
|
+
flags += ['--name-resolve', options[:name_resolve]] if options[:name_resolve]
|
330
|
+
flags += ['--configfile', (options[:configfile] ? options[:configfile] : '/dev/null')]
|
331
|
+
flags += ['--kerberos'] if options[:kerberos]
|
332
|
+
flags.map(&:to_s)
|
333
|
+
end
|
289
334
|
end
|
290
335
|
end
|
data/lib/sambal/test_server.rb
CHANGED
@@ -39,7 +39,7 @@ module Sambal
|
|
39
39
|
@erb_path = "#{File.expand_path(File.dirname(__FILE__))}/smb.conf.erb"
|
40
40
|
@host = "127.0.0.1" ## will always just be localhost
|
41
41
|
@root_path = File.expand_path(File.dirname(File.dirname(File.dirname(__FILE__))))
|
42
|
-
@tmp_path = "#{root_path}/spec_tmp"
|
42
|
+
@tmp_path = ENV.key?('SAMBAL_TEMP_PATH') ? ENV['SAMBAL_TEMP_PATH'] : "#{root_path}/spec_tmp"
|
43
43
|
@share_path = "#{tmp_path}/share"
|
44
44
|
@share_name = share_name
|
45
45
|
@config_path = "#{tmp_path}/smb.conf"
|
@@ -71,11 +71,11 @@ module Sambal
|
|
71
71
|
def start
|
72
72
|
if RUBY_PLATFORM=="java"
|
73
73
|
@smb_server_pid = Thread.new do
|
74
|
-
`smbd -
|
74
|
+
`smbd -F -s #{@config_path} -p #{@port} --option="lockdir"=#{@lock_path} --option="pid directory"=#{@pid_dir} --option="private directory"=#{@private_dir} --option="cache directory"=#{@cache_dir} --option="state directory"=#{@state_dir} < /dev/null > #{@log_path}/smb.log`
|
75
75
|
end
|
76
76
|
else
|
77
77
|
@smb_server_pid = fork do
|
78
|
-
|
78
|
+
exec "smbd -F -s #{@config_path} -p #{@port} --option=\"lockdir\"=#{@lock_path} --option=\"pid directory\"=#{@pid_dir} --option=\"private directory\"=#{@private_dir} --option=\"cache directory\"=#{@cache_dir} --option=\"state directory\"=#{@state_dir} < /dev/null > #{@log_path}/smb.log"
|
79
79
|
end
|
80
80
|
end
|
81
81
|
sleep 2 ## takes a short time to start up
|
data/lib/sambal/version.rb
CHANGED
data/spec/sambal/client_spec.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
require 'tempfile'
|
5
|
+
require 'tmpdir'
|
5
6
|
|
6
7
|
describe Sambal::Client do
|
7
8
|
|
@@ -140,6 +141,17 @@ describe Sambal::Client do
|
|
140
141
|
end
|
141
142
|
end
|
142
143
|
|
144
|
+
describe 'rename' do
|
145
|
+
it 'is successful when renaming an existing file' do
|
146
|
+
expect(@sambal_client.rename(TESTFILE, 'renamed_file.txt')).to be_successful
|
147
|
+
expect(File.exists?(File.join(test_server.share_path, 'renamed_file.txt'))).to eq true
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'is unsuccessful when the file does not exist' do
|
151
|
+
expect(@sambal_client.rename('unknown_file.txt', 'renamed_file.txt')).not_to be_successful
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
143
155
|
it "should get files from an smb server" do
|
144
156
|
expect(@sambal_client.get(TESTFILE, "/tmp/sambal_spec_testfile.txt")).to be_successful
|
145
157
|
expect(File.exists?("/tmp/sambal_spec_testfile.txt")).to eq true
|
@@ -246,4 +258,44 @@ describe Sambal::Client do
|
|
246
258
|
expect(@sambal_client.wrap_filenames('cmd',[Pathname.new('file1'), Pathname.new('file2')])).to eq('cmd "file1" "file2"')
|
247
259
|
end
|
248
260
|
|
261
|
+
it 'should prevent smb command injection by malicious filename' do
|
262
|
+
expect(@sambal_client.exists?('evil.txt')).to be_falsy
|
263
|
+
@sambal_client.ls("\b\b\b\bput \"#{file_to_upload.path}\" \"evil.txt")
|
264
|
+
expect(@sambal_client.exists?('evil.txt')).to be_falsy
|
265
|
+
end
|
266
|
+
|
267
|
+
describe 'sanitize_filename' do
|
268
|
+
it 'should remove unprintable character' do
|
269
|
+
expect(@sambal_client.sanitize_filename("fi\b\ble\n name\r\n")).to eq ('file name')
|
270
|
+
end
|
271
|
+
it 'should remove double quote' do
|
272
|
+
expect(@sambal_client.sanitize_filename('double"quote')).to eq ('doublequote')
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe 'malicious flags' do
|
277
|
+
it 'should not inject command by hostname' do
|
278
|
+
Dir.mktmpdir do |dir|
|
279
|
+
begin
|
280
|
+
client = described_class.new(host: "\"; touch #{dir}/evil.txt;\"", share: test_server.share_name, port: test_server.port)
|
281
|
+
rescue
|
282
|
+
ensure
|
283
|
+
client.close if client
|
284
|
+
end
|
285
|
+
expect(File.exists?("#{dir}/evil.txt")).to be_falsy
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'should not inject command by domain' do
|
290
|
+
Dir.mktmpdir do |dir|
|
291
|
+
begin
|
292
|
+
client = described_class.new(host: test_server.host, share: test_server.share_name, port: test_server.share_name, domain: "\"; touch #{dir}/evil.txt; ls \"")
|
293
|
+
rescue
|
294
|
+
ensure
|
295
|
+
client.close if client
|
296
|
+
end
|
297
|
+
expect(File.exists?("#{dir}/evil.txt")).to be_falsy
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
249
301
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sambal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Axel Eriksson
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -31,26 +31,20 @@ executables: []
|
|
31
31
|
extensions: []
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
|
-
- ".gitignore"
|
35
|
-
- Gemfile
|
36
34
|
- LICENSE
|
37
35
|
- README.md
|
38
|
-
- Rakefile
|
39
|
-
- Spookfile
|
40
|
-
- circle.yml
|
41
36
|
- lib/sambal.rb
|
42
37
|
- lib/sambal/client.rb
|
43
38
|
- lib/sambal/response.rb
|
44
39
|
- lib/sambal/smb.conf.erb
|
45
40
|
- lib/sambal/test_server.rb
|
46
41
|
- lib/sambal/version.rb
|
47
|
-
- sambal.gemspec
|
48
42
|
- spec/sambal/client_spec.rb
|
49
43
|
- spec/spec_helper.rb
|
50
44
|
homepage: https://github.com/johnae/sambal
|
51
45
|
licenses: []
|
52
46
|
metadata: {}
|
53
|
-
post_install_message:
|
47
|
+
post_install_message:
|
54
48
|
rdoc_options: []
|
55
49
|
require_paths:
|
56
50
|
- lib
|
@@ -65,9 +59,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
59
|
- !ruby/object:Gem::Version
|
66
60
|
version: '0'
|
67
61
|
requirements: []
|
68
|
-
|
69
|
-
|
70
|
-
signing_key:
|
62
|
+
rubygems_version: 3.2.26
|
63
|
+
signing_key:
|
71
64
|
specification_version: 4
|
72
65
|
summary: Ruby Samba Client
|
73
66
|
test_files:
|
data/.gitignore
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/Spookfile
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
-- How much output do we want?
|
2
|
-
log_level "INFO"
|
3
|
-
|
4
|
-
rspec = command "bundle exec rspec -f d"
|
5
|
-
|
6
|
-
watch "lib", "spec", ->
|
7
|
-
on_changed "^(spec)/(spec_helper%.rb)", -> rspec "spec"
|
8
|
-
on_changed "^spec/(.*)_spec%.rb", (a) -> rspec "spec/#{a}_spec.rb"
|
9
|
-
on_changed "^lib/sambal%.rb", (a) -> rspec "spec/sambal/client_spec.rb"
|
10
|
-
|
11
|
-
notifier "#{os.getenv('HOME')}/.spook/notifiers"
|
data/circle.yml
DELETED
data/sambal.gemspec
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
require File.expand_path('../lib/sambal/version', __FILE__)
|
3
|
-
|
4
|
-
Gem::Specification.new do |gem|
|
5
|
-
gem.authors = ["John Axel Eriksson"]
|
6
|
-
gem.email = ["john@insane.se"]
|
7
|
-
gem.description = %q{Ruby Samba Client using the cmdline smbclient}
|
8
|
-
gem.summary = %q{Ruby Samba Client}
|
9
|
-
gem.homepage = "https://github.com/johnae/sambal"
|
10
|
-
|
11
|
-
gem.files = `git ls-files`.split($\)
|
12
|
-
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
-
gem.name = "sambal"
|
15
|
-
gem.require_paths = ["lib"]
|
16
|
-
gem.version = Sambal::VERSION
|
17
|
-
|
18
|
-
gem.add_development_dependency "rspec", '>=3.4.0'
|
19
|
-
end
|