net-scp 3.0.0 → 4.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0350b7b1df8977a9ae7ac00b5c82a65d26db8fb23296582d21d7f944f04ad26e
4
- data.tar.gz: 29e3e812e3c338b67eab704a59dc1365dd5abaeeda08fd5a5ac7419116f4e82e
3
+ metadata.gz: b666f903f4a48fb442f7ddea7efbdf5a222a44b7e2c9f7ae0cba5b6b8a126e10
4
+ data.tar.gz: 39032bdfeb545761cd2229af060ca0a79855e051ecab1123e2c68100deb03e19
5
5
  SHA512:
6
- metadata.gz: 5522c65c85440ca5d95a8a320ed57d635cf2726f9e3d6a02056dd915c753c279368477fd98e2e379e4e5215e164b5c917d32ebcf35aec0734a0816f30ab7ee06
7
- data.tar.gz: 8d5ae8236f938551246216f86ec0d059187a971745c614e6362ce1928a878b3450936bfaa6719c77c9bdff4506272ef2623e5844fe5c923b71e8df2bdb166c7a
6
+ metadata.gz: 8608726350662b31607e5beaa21d0276fa93942b045ac29a5762236b99b5a57925f9fde59a9fb59ebdd6ada2d6ccb1f52aa17f90e7ee196d2c7a714d66afce2d
7
+ data.tar.gz: 3c4112b0662c5401f6bc4e50aec2797a583faa5d67947ee3e46c2dd7f1d48ed074f49df9893910d08ea09f0625b14071d1d66d8f7390bff66dee60e1e7d19095
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1 @@
1
+ <!-- After opening the PR, add a changelog.d/<PR number>.<type>.txt fragment (none, change, security, breaking or breaking-security) instead of editing CHANGES.txt; see CONTRIBUTING.md#changelog. -->
@@ -0,0 +1,18 @@
1
+ name: Changelog
2
+
3
+ on:
4
+ pull_request:
5
+ types: [opened, synchronize, reopened, labeled, unlabeled]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ changelog:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ # HEAD is the PR merged into the base branch; HEAD^1 is the base it was merged into.
17
+ fetch-depth: 2
18
+ - uses: net-ssh/changelogd@v1
@@ -0,0 +1,40 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ push: { branches: master }
5
+ permissions:
6
+ contents: read
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0', 'ruby-head']
14
+ continue-on-error: ${{ matrix.ruby-version == 'ruby-head' }}
15
+ steps:
16
+ - uses: actions/checkout@v3
17
+ - name: Set up Ruby ${{ matrix.ruby-version }}
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: ${{ matrix.ruby-version }}
21
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
22
+ - name: Run Tests
23
+ run: bundle exec rake test
24
+ env:
25
+ RUBYOPT: "--enable=frozen_string_literal"
26
+ test-legacy:
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ ruby-version: ['2.3', '2.4', '2.5']
32
+ steps:
33
+ - uses: actions/checkout@v3
34
+ # Plain docker run rather than container: the ruby:2.3/2.4 images' glibc is too old for the actions' Node.
35
+ # Not rake test: the Rakefile loads changelogd, which these Rubies can't install.
36
+ # No frozen_string_literal: net-ssh 6 mutates string literals.
37
+ - name: Run Tests
38
+ run: >-
39
+ docker run --rm -v "$PWD:/w" -w /w -e BUNDLE_GEMFILE=gemfiles/legacy.gemfile ruby:${{ matrix.ruby-version }}
40
+ sh -c 'bundle install --jobs 4 && bundle exec ruby -Ilib -Itest -e "Dir.glob(\"./test/test_*.rb\").each { |f| require f }"'
data/.gitignore CHANGED
@@ -4,3 +4,11 @@ doc
4
4
  *.swp
5
5
 
6
6
  .DS_Store
7
+
8
+ # Bundle local files
9
+ /vendor/
10
+ .bundle/config
11
+ Gemfile.lock
12
+
13
+ lib/net/ssh/version.rb.old
14
+ /gemfiles/*.lock
data/CHANGES.txt CHANGED
@@ -1,3 +1,35 @@
1
+ === 4.1.3
2
+
3
+ * Load again on Ruby 2.3-2.5 (4.1.2 needed 2.5), and declare `required_ruby_version >= 2.3` [#112, #110]
4
+ * Add a `quote:` option to `upload`/`download` for the remote path: `:posix` (default), `:windows` for Win32-OpenSSH under cmd.exe or PowerShell (double-quoted, `\` sent as `/`, paths containing `"`, `$`, `` ` ``, `%`, `!`, curly quotes, CR, LF or NUL rejected), `:none` to send the path unchanged to servers without a shell, or a callable. By default a leading `~` or `~/` is now left for the remote shell to expand to the home directory; such paths previously referred to a literal `~` entry in the login directory and usually failed. Thanks @ArtsiomMusin for the Windows patch in #34 [#109, #34, #9]
5
+ * Recursive uploads no longer abort on a broken symlink: it is skipped with a warning, the rest is uploaded, and a Net::SCP::Error listing the skipped paths is raised at the end, like OpenSSH scp. Based on #23 by @maage. [#108, #23]
6
+
7
+ === 4.1.2
8
+
9
+ * Security: Downloads no longer apply setuid, setgid or sticky bits sent by the server, check that a server-chosen local name matches the requested remote basename (as OpenSSH does since CVE-2019-6111), and refuse to follow an existing local symlink at a server-named path instead of writing through it. The local path passed by the caller may still be a symlink. O_NOFOLLOW suggested in net-sftp#162 by srosenhamer [#104]
10
+ * Security: Recursive downloads, and downloads into a directory, reject file names from the server that are not a single path component (such as `../x` or `a/b`) instead of writing outside the target directory. Reported in net-sftp#162 by srosenhamer [#103]
11
+ * Raise `Net::SCP::Error` when the server ends a transfer before the SCP protocol finishes, instead of reporting a truncated transfer as successful [#105, #96]
12
+ * Opening an `scp://` URI through `uri/open-scp` now percent-decodes the user, password and path (once, keeping a literal `+`) and passes an IPv6 address to SSH without its brackets, so `scp://user@[::1]/a%20b` downloads `/a b` from `::1`. Thanks @OskarEichler [#94]
13
+ * Opening an `scp://` URI through `uri/open-scp` now writes the download into the OpenURI buffer as it arrives, so large files spill to a temporary file instead of being held in memory twice. Thanks @OskarEichler [#93]
14
+ * Parsing an `scp://` URI with a valueless query entry (`?flag`) or an empty one (`?a=1&&b=2`) no longer raises `NoMethodError`, and `URI::SCP#options` reflects the current query after `query=`. Thanks @OskarEichler [#92]
15
+ * Opening an `scp://` URI through `uri/open-scp` now calls `:progress_proc` with the number of bytes received, as OpenURI documents, instead of with the SCP progress arguments (which raised `ArgumentError` for a lambda), and calls `:content_length_proc` once with the file size. Thanks @OskarEichler [#91]
16
+ * Opening an `scp://` URI through `uri/open-scp` now uses the port and password given in the URI; they were passed outside the `:ssh` options and ignored. Options given in `:ssh` still take precedence. Thanks @OskarEichler [#90]
17
+ * Close the local file net-scp opened for an upload or download when the transfer fails, so it is not left open (and locked on Windows) [#89, #20]
18
+ * With `preserve: true`, recursive downloads now keep directories' modification times, which were overwritten when their contents were created. Thanks @OskarEichler [#88]
19
+ * `upload`/`upload!` with a zero, negative or non-Integer `:chunk_size` now raises `ArgumentError` before opening a channel; previously `0` hung forever sending empty chunks, a negative value or a String failed only after the channel was open, and a Float was silently truncated. Thanks @OskarEichler [#87]
20
+ * Uploading from an IO that isn't at its start now announces only the remaining bytes, instead of the full size, which made the transfer hang or fail. Thanks @OskarEichler [#86]
21
+ * Accept a `Pathname` as the local path for uploads and downloads; previously uploads looped forever and downloads kept only the last chunk. Thanks @OskarEichler [#85, #29]
22
+
23
+ === 4.1.1
24
+
25
+ * Allow net-ssh 8
26
+ * Fix frozen string literal compatibility [npezza93, mfazekas]
27
+ - Use mutable string for scp command construction
28
+ - Use mutable string for error_string initialization
29
+ * Add Ruby 3.4 and ruby-head to CI matrix
30
+ * Add Ruby 4.0+ compatibility (logger, rdoc gems)
31
+ * Drop EOL Ruby 2.3-2.5 from CI
32
+
1
33
  === 3.0.0
2
34
 
3
35
  * NetSHH 6.* support
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,33 @@
1
+ # Contributing to net-scp
2
+
3
+ Run the tests with `bundle exec rake test`.
4
+
5
+ ## Changelog
6
+
7
+ Don't edit `CHANGES.txt`. Instead, add a fragment file to `changelog.d/` in your PR; the release's version bump (`rake vbump:*`) moves all fragments into a new `=== <version>` section at the top of `CHANGES.txt` and deletes them.
8
+
9
+ A fragment is named `<number>.<type>.txt`, where `<number>` is the PR number (so open the PR first, then add the fragment), and contains a single UTF-8 paragraph: the entry as it should read in `CHANGES.txt`, without the bullet or prefix. Lines are joined, so it can be wrapped. If it doesn't end with a reference list such as `[#95, #101]` or `[GHSA-xxxx-xxxx-xxxx]`, ` [#<number>]` is appended.
10
+
11
+ | Type | Written as | Use for |
12
+ | ------------------- | ------------------------------ | ------------------------------------------------------------ |
13
+ | `breaking-security` | `* BREAKING: Security: <text>` | an incompatible change made for security |
14
+ | `breaking` | `* BREAKING: <text>` | an incompatible change |
15
+ | `security` | `* Security: <text>` | a security fix or hardening |
16
+ | `change` | `* <text>` | any other user-visible fix or feature |
17
+ | `none` | nothing | PRs with no user-visible change (tests, CI, docs, refactors) |
18
+
19
+ Examples:
20
+
21
+ ```sh
22
+ rake "changelog:new[102,change,Fixed uploading files with spaces in their names]"
23
+ # changelog.d/102.change.txt -> * Fixed uploading files with spaces in their names [#102]
24
+
25
+ echo 'Refuse downloads that write outside the target directory [GHSA-xxxx-xxxx-xxxx]' > changelog.d/103.security.txt
26
+ touch changelog.d/104.none.txt
27
+ ```
28
+
29
+ For several entries from one PR add a suffix: `102.change.txt`, `102.change.2.txt` (`changelog:new` picks the next free one).
30
+
31
+ `rake changelog:check` validates the fragments and `rake changelog:preview` prints the section the next release would add. CI fails a PR that edits `CHANGES.txt` or doesn't add or modify a `changelog.d/<PR number>.<type>.txt` fragment, unless it only touches `changelog.d/` or a maintainer adds the `skip-changelog` label; Dependabot PRs are skipped.
32
+
33
+ The full rules, including how a release orders the entries, are in the [changelogd README](https://github.com/net-ssh/changelogd#readme).
data/Gemfile CHANGED
@@ -3,9 +3,16 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in mygem.gemspec
4
4
  gemspec
5
5
 
6
+ gem "net-ssh", ">= 7.3.1"
7
+ gem "logger" # no longer a default gem in Ruby 4.0+
8
+
6
9
  # TODO: add to gemspec
7
- gem "bundler", "~> 1.11"
8
- gem "rake", "~> 12.0"
10
+ gem "bundler", ">= 1.11"
11
+ gem "rake", ">= 12.0"
12
+ gem "rdoc"
13
+
14
+ # git: rather than github:, which Bundler 1.x (the publish workflow uses 1.17.3) expands to an unsupported git:// URL.
15
+ gem "changelogd", git: "https://github.com/net-ssh/changelogd.git", tag: "v1", group: :development
9
16
 
10
17
  gem 'byebug', group: %i[development test] if !Gem.win_platform? && RUBY_ENGINE == "ruby"
11
18
 
data/Manifest CHANGED
@@ -7,7 +7,7 @@ lib/net/scp.rb
7
7
  lib/uri/open-scp.rb
8
8
  lib/uri/scp.rb
9
9
  Rakefile
10
- README.rdoc
10
+ README.md
11
11
  setup.rb
12
12
  test/common.rb
13
13
  test/test_all.rb
data/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # Net::SCP
2
+
3
+ ***Please note: this project is in maintenance mode. It is not under active
4
+ development but pull requests are very much welcome. Just be sure to include
5
+ tests! -- delano***
6
+
7
+ * Docs: http://net-ssh.github.io/net-scp
8
+ * Issues: https://github.com/net-ssh/net-scp/issues
9
+ * Codes: https://github.com/net-ssh/net-scp
10
+ * Email: net-ssh@solutious.com
11
+
12
+
13
+ *As of v1.0.5, all gem releases are signed. See INSTALL.*
14
+
15
+ ## DESCRIPTION:
16
+
17
+ Net::SCP is a pure-Ruby implementation of the SCP protocol. This operates over
18
+ SSH (and requires the Net::SSH library), and allows files and directory trees
19
+ to be copied to and from a remote server.
20
+
21
+ ## FEATURES/PROBLEMS:
22
+
23
+ * Transfer files or entire directory trees to or from a remote host via SCP
24
+ * Can preserve file attributes across transfers
25
+ * Can download files in-memory, or direct-to-disk
26
+ * Support for SCP URI's, and OpenURI
27
+
28
+
29
+ ## SYNOPSIS:
30
+
31
+ In a nutshell:
32
+
33
+ ```ruby
34
+ require 'net/scp'
35
+
36
+ # upload a file to a remote server
37
+ Net::SCP.upload!("remote.host.com", "username",
38
+ "/local/path", "/remote/path",
39
+ :ssh => { :password => "password" })
40
+
41
+ # upload recursively
42
+ Net::SCP.upload!("remote.host", "username", "/path/to/local", "/path/to/remote",
43
+ :ssh => { :password => "foo" }, :recursive => true)
44
+
45
+ # download a file from a remote server
46
+ Net::SCP.download!("remote.host.com", "username",
47
+ "/remote/path", "/local/path",
48
+ :ssh => { :password => "password" })
49
+
50
+ # download a file to an in-memory buffer
51
+ data = Net::SCP::download!("remote.host.com", "username", "/remote/path")
52
+
53
+ # use a persistent connection to transfer files
54
+ Net::SCP.start("remote.host.com", "username", :password => "password") do |scp|
55
+ # upload a file to a remote server
56
+ scp.upload! "/local/path", "/remote/path"
57
+
58
+ # upload from an in-memory buffer
59
+ scp.upload! StringIO.new("some data to upload"), "/remote/path"
60
+
61
+ # run multiple downloads in parallel
62
+ d1 = scp.download("/remote/path", "/local/path")
63
+ d2 = scp.download("/remote/path2", "/local/path2")
64
+ [d1, d2].each { |d| d.wait }
65
+ end
66
+
67
+ # You can also use open-uri to grab data via scp:
68
+ require 'uri/open-scp'
69
+ data = open("scp://user@host/path/to/file.txt").read
70
+ ```
71
+
72
+ The remote path is escaped for a POSIX shell, except that a leading `~` or
73
+ `~/` is left for the remote shell to expand to the home directory (`~user/` is
74
+ not expanded). Use the `quote:` option of `upload`/`download` for other
75
+ servers:
76
+
77
+ ```ruby
78
+ # Win32-OpenSSH (cmd.exe or PowerShell): the path is double-quoted and "\" becomes "/".
79
+ # Paths containing ", $, `, %, !, curly quotes, CR, LF or NUL raise ArgumentError.
80
+ scp.upload! "/local/path", "C:\\Program Files\\App\\file.txt", quote: :windows
81
+
82
+ # Servers that don't run a shell (MikroTik and other appliances): the path is
83
+ # sent unchanged. Never use this with untrusted paths.
84
+ scp.download! "flash/backup.rsc", "/local/backup.rsc", quote: :none
85
+
86
+ # Or quote it yourself
87
+ scp.upload! "/local/path", remote, quote: ->(path) { "'#{path}'" }
88
+ ```
89
+
90
+ For more information, see Net::SCP.
91
+
92
+ ## REQUIREMENTS:
93
+
94
+ * Net::SSH 2
95
+
96
+ If you wish to run the tests, you'll also need:
97
+
98
+ * Echoe (for Rakefile use)
99
+ * Mocha (for tests)
100
+
101
+
102
+ ## INSTALL:
103
+
104
+ * ```gem install net-scp (might need sudo privileges)```
105
+
106
+
107
+ However, in order to be sure the code you're installing hasn't been tampered
108
+ with, it's recommended that you verify the
109
+ [signature](http://docs.seattlerb.org/rubygems/Gem/Security.html). To do this,
110
+ you need to add my public key as a trusted certificate (you only need to do
111
+ this once):
112
+
113
+ ```sh
114
+ # Add the public key as a trusted certificate
115
+ # (You only need to do this once)
116
+ $ curl -O https://raw.githubusercontent.com/net-ssh/net-ssh/master/net-ssh-public_cert.pem
117
+ $ gem cert --add net-ssh-public_cert.pem
118
+ ```
119
+
120
+ Then- when installing the gem - do so with high security:
121
+
122
+ $ gem install net-scp -P HighSecurity
123
+
124
+ If you don't add the public key, you'll see an error like "Couldn't verify
125
+ data signature". If you're still having trouble let me know and I'll give you
126
+ a hand.
127
+
128
+ Or, you can do it the hard way (without Rubygems):
129
+
130
+ * tar xzf net-scp-*.tgz
131
+ * cd net-scp-*
132
+ * ruby setup.rb config
133
+ * ruby setup.rb install (might need sudo privileges)
134
+
135
+ ## Security contact information
136
+
137
+ See [SECURITY.md](SECURITY.md)
138
+
139
+ ## LICENSE:
140
+
141
+ (The MIT License)
142
+
143
+ Copyright (c) 2008 Jamis Buck <jamis@37signals.com>
144
+
145
+ Permission is hereby granted, free of charge, to any person obtaining a copy
146
+ of this software and associated documentation files (the 'Software'), to deal
147
+ in the Software without restriction, including without limitation the rights
148
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
149
+ copies of the Software, and to permit persons to whom the Software is
150
+ furnished to do so, subject to the following conditions:
151
+
152
+ The above copyright notice and this permission notice shall be included in all
153
+ copies or substantial portions of the Software.
154
+
155
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
156
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
157
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
158
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
159
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
160
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
161
+ SOFTWARE.
data/Rakefile CHANGED
@@ -1,7 +1,10 @@
1
1
  require "rubygems"
2
2
  require "rake"
3
3
  require "rake/clean"
4
- require "rdoc/task"
4
+ begin
5
+ require "rdoc/task"
6
+ rescue LoadError
7
+ end
5
8
  require "bundler/gem_tasks"
6
9
 
7
10
  desc "When releasing make sure NET_SSH_BUILDGEM_SIGNED is set"
@@ -19,6 +22,16 @@ name = "net-scp"
19
22
  require_relative "lib/net/scp/version"
20
23
  version = Net::SCP::Version::CURRENT
21
24
 
25
+ begin
26
+ require "changelogd/rake"
27
+ rescue LoadError
28
+ # A git gem isn't on the load path under plain `rake`, as the release workflow runs it; bundler/setup could clash with the running rake.
29
+ require "bundler"
30
+ $LOAD_PATH.unshift(*Bundler.definition.specs["changelogd"].flat_map(&:full_require_paths))
31
+ require "changelogd/rake"
32
+ end
33
+ Changelogd::RakeTasks.new(version_file: "lib/net/scp/version.rb")
34
+
22
35
  namespace :cert do
23
36
  desc "Update public cert from private - only run if public is expired"
24
37
  task :update_public_when_expired do
@@ -67,15 +80,106 @@ Rake::TestTask.new do |t|
67
80
  end
68
81
 
69
82
  extra_files = %w[LICENSE.txt THANKS.txt CHANGES.txt ]
70
- RDoc::Task.new do |rdoc|
71
- rdoc.rdoc_dir = "rdoc"
72
- rdoc.title = "#{name} #{version}"
73
- rdoc.generator = 'hanna' # gem install hanna-nouveau
74
- rdoc.main = 'README.rdoc'
75
- rdoc.rdoc_files.include("README*")
76
- rdoc.rdoc_files.include("bin/*.rb")
77
- rdoc.rdoc_files.include("lib/**/*.rb")
78
- extra_files.each { |file|
79
- rdoc.rdoc_files.include(file) if File.exists?(file)
80
- }
83
+ if defined?(RDoc::Task)
84
+ RDoc::Task.new do |rdoc|
85
+ rdoc.rdoc_dir = "rdoc"
86
+ rdoc.title = "#{name} #{version}"
87
+ rdoc.generator = 'hanna' # gem install hanna-nouveau
88
+ rdoc.main = 'README.md'
89
+ rdoc.rdoc_files.include("README*")
90
+ rdoc.rdoc_files.include("bin/*.rb")
91
+ rdoc.rdoc_files.include("lib/**/*.rb")
92
+ extra_files.each { |file|
93
+ rdoc.rdoc_files.include(file) if File.exist?(file)
94
+ }
95
+ end
96
+ end
97
+
98
+ def change_version(&block)
99
+ Rake::Task["changelog:check"].invoke
100
+ version_file = 'lib/net/scp/version.rb'
101
+ require_relative version_file
102
+ pre = Net::SCP::Version::PRE
103
+ tiny = Net::SCP::Version::TINY
104
+ result = block[pre: pre, tiny: Net::SCP::Version::TINY]
105
+ raise ArgumentError, "Version change logic should always return a pre" unless result.key?(:pre)
106
+
107
+ new_pre = result[:pre]
108
+ new_tiny = result[:tiny] || tiny
109
+ found = { pre: false, tiny: false }
110
+ File.open("#{version_file}.new", "w") do |f|
111
+ File.readlines(version_file).each do |line|
112
+ match =
113
+ if pre.nil?
114
+ /^(\s+PRE\s+=\s+)nil(\s*)$/.match(line)
115
+ else
116
+ /^(\s+PRE\s+=\s+")#{pre}("\s*)$/.match(line)
117
+ end
118
+ if match
119
+ prefix = match[1]
120
+ postfix = match[2]
121
+ prefix.delete_suffix!('"')
122
+ postfix.delete_prefix!('"')
123
+ new_line = "#{prefix}#{new_pre.inspect}#{postfix}"
124
+ puts "Changing:\n - #{line} + #{new_line}"
125
+ line = new_line
126
+ found[:pre] = true
127
+ end
128
+
129
+ if new_tiny != tiny
130
+ match = /^(\s+TINY\s+=\s+)#{tiny}(\s*)$/.match(line)
131
+ if match
132
+ prefix = match[1]
133
+ postfix = match[2]
134
+ new_line = "#{prefix}#{new_tiny}#{postfix}"
135
+ puts "Changing:\n - #{line} + #{new_line}"
136
+ line = new_line
137
+ found[:tiny] = true
138
+ end
139
+ end
140
+
141
+ f.write(line)
142
+ end
143
+ raise ArgumentError, "Cound not find line: PRE = \"#{pre}\" in #{version_file}" unless found[:pre]
144
+ raise ArgumentError, "Cound not find line: TINY = \"#{tiny}\" in #{version_file}" unless found[:tiny] || new_tiny == tiny
145
+ end
146
+
147
+ FileUtils.mv version_file, "#{version_file}.old"
148
+ FileUtils.mv "#{version_file}.new", version_file
149
+ Rake::Task["changelog:release"].invoke
150
+ end
151
+
152
+ namespace :vbump do
153
+ desc "Final release"
154
+ task :final do
155
+ change_version do |pre:, tiny:|
156
+ _ = tiny
157
+ if pre.nil?
158
+ { tiny: tiny + 1, pre: nil }
159
+ else
160
+ raise ArgumentError, "Unexpected pre: #{pre}" if pre.nil?
161
+
162
+ { pre: nil }
163
+ end
164
+ end
165
+ end
166
+
167
+ desc "Increment prerelease"
168
+ task :pre, [:type] do |_t, args|
169
+ change_version do |pre:, tiny:|
170
+ puts " PRE => #{pre.inspect}"
171
+ match = /^([a-z]+)(\d+)/.match(pre)
172
+ raise ArgumentError, "Unexpected pre: #{pre}" if match.nil? && args[:type].nil?
173
+
174
+ if match.nil? || (!args[:type].nil? && args[:type] != match[1])
175
+ if pre.nil?
176
+ { pre: "#{args[:type]}1", tiny: tiny + 1 }
177
+ else
178
+ { pre: "#{args[:type]}1" }
179
+ end
180
+ else
181
+ { pre: "#{match[1]}#{match[2].to_i + 1}" }
182
+ end
183
+ end
184
+ end
81
185
  end
data/SECURITY.md ADDED
@@ -0,0 +1,4 @@
1
+ ## Security contact information
2
+
3
+ To report a security vulnerability, please use the
4
+ [GitHub vulnerability reporting feature](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability).
@@ -0,0 +1 @@
1
+ Changelog fragments: one `<PR number>.<type>.txt` file per user-visible change, assembled into `CHANGES.txt` when the version is bumped. See [CONTRIBUTING.md](../CONTRIBUTING.md#changelog).
@@ -0,0 +1,7 @@
1
+ # Ruby 2.3-2.5: the last net-ssh and mocha releases that still install there.
2
+ source 'https://rubygems.org'
3
+
4
+ gemspec path: ".."
5
+
6
+ gem "net-ssh", "< 7"
7
+ gem "mocha", "< 2"
@@ -50,8 +50,11 @@ module Net; class SCP
50
50
  when :file
51
51
  read_file(channel, directive)
52
52
  when :end
53
+ directory = channel[:stack].pop
54
+ if channel[:options][:preserve] && directory && directory[:times]
55
+ File.utime(directory[:times][:atime], directory[:times][:mtime], channel[:local])
56
+ end
53
57
  channel[:local] = File.dirname(channel[:local])
54
- channel[:stack].pop
55
58
  channel[:state] = :finish if channel[:stack].empty?
56
59
  end
57
60
 
@@ -73,7 +76,7 @@ module Net; class SCP
73
76
  # Finishes off the read, sets the times for the file (if any), and then
74
77
  # jumps to either #finish_state (for single-file downloads) or
75
78
  # #read_directive_state (for recursive downloads). A 0-byte is sent to the
76
- # server to indicate that the file was recieved successfully.
79
+ # server to indicate that the file was received successfully.
77
80
  def finish_read_state(channel)
78
81
  channel[:io].close unless channel[:io] == channel[:local]
79
82
 
@@ -126,19 +129,17 @@ module Net; class SCP
126
129
  raise Net::SCP::Error, ":recursive not specified for directory download"
127
130
  end
128
131
 
129
- channel[:local] = File.join(channel[:local], directive[:name])
132
+ channel[:local] = local_path_for(channel, directive[:name])
130
133
 
131
- if File.exist?(channel[:local]) && !File.directory?(channel[:local])
134
+ if File.symlink?(channel[:local])
135
+ raise Net::SCP::Error, "#{channel[:local]} is a symlink, refusing to follow it"
136
+ elsif File.exist?(channel[:local]) && !File.directory?(channel[:local])
132
137
  raise Net::SCP::Error, "#{channel[:local]} already exists and is not a directory"
133
138
  elsif !File.exist?(channel[:local])
134
- Dir.mkdir(channel[:local], directive[:mode] | 0700)
139
+ Dir.mkdir(channel[:local], (directive[:mode] & 0777) | 0700)
135
140
  end
136
141
 
137
- if channel[:options][:preserve] && channel[:times]
138
- File.utime(channel[:times][:atime], channel[:times][:mtime], channel[:local])
139
- end
140
-
141
- channel[:stack] << directive
142
+ channel[:stack] << directive.merge(:times => channel[:times])
142
143
  channel[:times] = nil
143
144
  end
144
145
 
@@ -146,20 +147,54 @@ module Net; class SCP
146
147
  # actual read.
147
148
  def read_file(channel, directive)
148
149
  if !channel[:local].respond_to?(:write)
149
- directive[:name] = (channel[:options][:recursive] || File.directory?(channel[:local])) ?
150
- File.join(channel[:local], directive[:name]) :
151
- channel[:local]
150
+ server_named = channel[:options][:recursive] || File.directory?(channel[:local])
151
+ directive[:name] = server_named ? local_path_for(channel, directive[:name]) : channel[:local]
152
152
  end
153
153
 
154
154
  channel[:file] = directive.merge(:times => channel[:times])
155
+ # Setuid/setgid/sticky bits are never taken from the server, even with :preserve.
155
156
  channel[:io] = channel[:local].respond_to?(:write) ? channel[:local] :
156
- File.new(directive[:name], "wb", directive[:mode] | 0600)
157
+ open_local_file(directive[:name], (directive[:mode] & 0777) | 0600, server_named)
157
158
  channel[:times] = nil
158
159
  channel[:remaining] = channel[:file][:size]
159
160
  channel[:state] = :read_data
160
161
 
161
162
  progress_callback(channel, channel[:file][:name], 0, channel[:file][:size])
162
163
  end
164
+
165
+ # The name comes from the server, so anything but a single path component
166
+ # would let a malicious server write outside the target (CVE-2019-6111).
167
+ def local_path_for(channel, name)
168
+ if name.empty? || name == "." || name == ".." || name.include?("/") || name.include?("\0") ||
169
+ (File::ALT_SEPARATOR && name.include?(File::ALT_SEPARATOR))
170
+ raise Net::SCP::Error, "invalid file name from server: #{name.inspect}"
171
+ end
172
+
173
+ if channel[:stack].empty? && !requested_name?(channel[:remote], name)
174
+ raise Net::SCP::Error, "file name from server does not match request: #{name.inspect}"
175
+ end
176
+
177
+ File.join(channel[:local], name)
178
+ end
179
+
180
+ # OpenSSH's CVE-2019-6111 check: the flags mirror its brace expansion plus fnmatch(3) with flags 0.
181
+ def requested_name?(remote, name)
182
+ pattern = File.basename(remote.to_s)
183
+ name == pattern || File.fnmatch(pattern, name, File::FNM_DOTMATCH | File::FNM_EXTGLOB)
184
+ end
185
+
186
+ # A symlink planted at a server-named path must not redirect the write;
187
+ # the caller's own target may be a symlink on purpose (net-sftp#162).
188
+ def open_local_file(path, perm, nofollow)
189
+ return File.new(path, "wb", perm) unless nofollow && defined?(File::NOFOLLOW)
190
+
191
+ begin
192
+ File.new(path, "wb", perm, :flags => File::NOFOLLOW)
193
+ rescue SystemCallError
194
+ raise unless File.symlink?(path)
195
+ raise Net::SCP::Error, "#{path} is a symlink, refusing to follow it"
196
+ end
197
+ end
163
198
  end
164
199
 
165
200
  end; end