mirror of
https://git.freebsd.org/ports.git
synced 2025-05-25 07:26:29 -04:00
Add sunshar, a "secure unshar" for ports committers, which:
- Does not execute unknown commands nor call sh(1) at all. - Does not overwrite existing files by default. - Does not extract files into upper directories. - Does have a dry run (-n) flag to see what would have been extracted. - Does have a strip (-p N) flag to strip any number of levels from pathnames. It (so far) only supports shell archives made with BSD shar.
This commit is contained in:
parent
da8b221743
commit
ac6314f977
Notes:
svn2git
2021-03-31 03:12:20 +00:00
svn path=/head/; revision=102409
3 changed files with 368 additions and 0 deletions
299
Tools/scripts/sunshar.rb
Executable file
299
Tools/scripts/sunshar.rb
Executable file
|
@ -0,0 +1,299 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
# -*- ruby -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2001-2004 Akinori MUSHA
|
||||||
|
#
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions
|
||||||
|
# are met:
|
||||||
|
# 1. Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# $FreeBSD$
|
||||||
|
|
||||||
|
RCS_ID = %q$Idaemons: /home/cvs/sunshar/sunshar.rb,v 1.13 2004/02/28 14:15:47 knu Exp $
|
||||||
|
RCS_REVISION = RCS_ID.split[2]
|
||||||
|
MYNAME = File.basename($0)
|
||||||
|
|
||||||
|
require 'parsearg'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'shellwords'
|
||||||
|
require 'stringio'
|
||||||
|
|
||||||
|
begin
|
||||||
|
require 'features/ruby18/dir'
|
||||||
|
rescue LoadError; end
|
||||||
|
|
||||||
|
$USAGE = 'usage'
|
||||||
|
|
||||||
|
$strip_level = 0
|
||||||
|
$force = false
|
||||||
|
$dryrun = false
|
||||||
|
$quiet = false
|
||||||
|
$dir = nil
|
||||||
|
|
||||||
|
def info(*s)
|
||||||
|
puts(*s) unless $quiet
|
||||||
|
end
|
||||||
|
|
||||||
|
def usage
|
||||||
|
print <<-EOF
|
||||||
|
#{MYNAME} rev.#{RCS_REVISION}
|
||||||
|
|
||||||
|
usage: #{MYNAME} [-fnq] [-p level] [file]
|
||||||
|
#{MYNAME} -h
|
||||||
|
-d dir chdir -- chdir to dir before extracting files
|
||||||
|
-f force -- allow overwriting, ignore errors
|
||||||
|
-h help -- show this help
|
||||||
|
-n dry run -- show what would have been extracted
|
||||||
|
-p N strip -- strip N levels from pathnames (cf. patch(1)\'s -p)
|
||||||
|
-q quiet -- be quiet
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
|
||||||
|
def main
|
||||||
|
parseArgs(0, nil, 'fhnq', 'd:', 'p:')
|
||||||
|
|
||||||
|
if $OPT_h
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if $OPT_f
|
||||||
|
$force = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if $OPT_n
|
||||||
|
$dryrun = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if $OPT_q
|
||||||
|
$quiet = true
|
||||||
|
end
|
||||||
|
|
||||||
|
$dir = $OPT_d || '.'
|
||||||
|
|
||||||
|
if $OPT_p
|
||||||
|
$strip_level = $OPT_p.to_i rescue -1
|
||||||
|
|
||||||
|
if $strip_level < 0
|
||||||
|
STDERR.puts "negative value ignored: #{$OPT_p}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
nerrors = 0
|
||||||
|
|
||||||
|
if ARGV.empty?
|
||||||
|
info "extracting files from stdin into #{$dir}"
|
||||||
|
|
||||||
|
begin
|
||||||
|
Dir.chdir($dir) {
|
||||||
|
unshar_stream(STDIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
info "done."
|
||||||
|
rescue => e
|
||||||
|
STDERR.puts "error in extracting stdin: #{e.message}"
|
||||||
|
nerrors += 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for file in ARGV
|
||||||
|
info "extracting files from #{file} into #{$dir}"
|
||||||
|
|
||||||
|
begin
|
||||||
|
File.open(file) do |f|
|
||||||
|
Dir.chdir($dir) {
|
||||||
|
unshar_stream(f)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
info "done."
|
||||||
|
rescue => e
|
||||||
|
STDERR.puts "error in extracting #{file}: #{e.message}"
|
||||||
|
nerrors += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
exit nerrors
|
||||||
|
end
|
||||||
|
|
||||||
|
def unshar_stream(io)
|
||||||
|
e = nil
|
||||||
|
|
||||||
|
while line = io.gets
|
||||||
|
if /^(\s*)\# This is a shell archive/ =~ line
|
||||||
|
indent = $1.length
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if io.eof?
|
||||||
|
raise "not a shell archive."
|
||||||
|
end
|
||||||
|
|
||||||
|
f = nil
|
||||||
|
prefix = nil
|
||||||
|
file = nil
|
||||||
|
boundary = nil
|
||||||
|
|
||||||
|
while line = io.gets
|
||||||
|
line.slice!(0, indent)
|
||||||
|
|
||||||
|
if f
|
||||||
|
if line.strip == boundary
|
||||||
|
f.close
|
||||||
|
f = nil
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
if line.sub!(/^#{Regexp.quote(prefix)}/, '')
|
||||||
|
f.print line
|
||||||
|
else
|
||||||
|
raise "line #{io.lineno}: broken archive: #{line}"
|
||||||
|
end
|
||||||
|
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
case line
|
||||||
|
when /^exit\s*$/
|
||||||
|
break
|
||||||
|
when /^echo\s+(.+)$/
|
||||||
|
# info $1
|
||||||
|
when /^mkdir\s+(?:-p\s+)?(.+)$/
|
||||||
|
dir = nil
|
||||||
|
|
||||||
|
Shellwords.shellwords($1).each do |word|
|
||||||
|
if /^[^\-]/ =~ word
|
||||||
|
dir = word
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
next if dir.nil?
|
||||||
|
|
||||||
|
dir = strip_filename(dir.strip + '/')
|
||||||
|
if dir.chomp('/').empty?
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
FileUtils.mkdir_p(dir) unless $dryrun
|
||||||
|
info "c - #{dir}"
|
||||||
|
rescue => e
|
||||||
|
info "c - #{dir} ... failed: #{e.message}"
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
when /sed\s+(.+)>(.+)<<(.+)/
|
||||||
|
prefix = Shellwords.shellwords($1).first
|
||||||
|
file = Shellwords.shellwords($2).first
|
||||||
|
boundary = Shellwords.shellwords($3).first
|
||||||
|
|
||||||
|
next unless prefix && file && boundary
|
||||||
|
|
||||||
|
if /s(.)\^(.*)\1\1/ =~ prefix
|
||||||
|
prefix = $2
|
||||||
|
else
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
file = strip_filename(file)
|
||||||
|
|
||||||
|
next if file.empty? || boundary.empty?
|
||||||
|
|
||||||
|
overwrite = false
|
||||||
|
|
||||||
|
if File.exist?(file)
|
||||||
|
if $force
|
||||||
|
overwrite = true
|
||||||
|
else
|
||||||
|
info "x - #{file} ... skipped"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
dir = File.dirname(file)
|
||||||
|
|
||||||
|
if !File.directory?(dir + "/.")
|
||||||
|
begin
|
||||||
|
FileUtils.mkdir_p(dir) unless $dryrun
|
||||||
|
info "d - #{dir}"
|
||||||
|
rescue => e
|
||||||
|
info "d - #{dir} ... failed: #{e.message}"
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
f = $dryrun ? StringIO.new : File.open(file, 'w')
|
||||||
|
if overwrite
|
||||||
|
info "x - #{file} ... overwritten"
|
||||||
|
else
|
||||||
|
info "x - #{file}"
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
info "x - #{file} ... failed! (#{e.message})"
|
||||||
|
|
||||||
|
if $force
|
||||||
|
f = nil
|
||||||
|
next
|
||||||
|
else
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
raise e if e
|
||||||
|
end
|
||||||
|
|
||||||
|
def strip_filename(file)
|
||||||
|
sfile = file.gsub(%r"/{2,}", "/")
|
||||||
|
|
||||||
|
if 0 < $strip_level
|
||||||
|
sfile.sub!(%r"^([^/]*/){1,#{$strip_level}}", '')
|
||||||
|
end
|
||||||
|
|
||||||
|
case sfile
|
||||||
|
when %r"^[~/]"
|
||||||
|
raise "reference to absolute directory: #{file} (use -p N)"
|
||||||
|
when %r"(^|/)\.\.(?:/|$)"
|
||||||
|
raise "reference to parent directory: #{file} (use -p N)"
|
||||||
|
end
|
||||||
|
|
||||||
|
sfile
|
||||||
|
end
|
||||||
|
|
||||||
|
def signal_handler(sig)
|
||||||
|
info "\nInterrupted."
|
||||||
|
|
||||||
|
exit 255
|
||||||
|
end
|
||||||
|
|
||||||
|
if $0 == __FILE__
|
||||||
|
for sig in [2, 3, 15]
|
||||||
|
trap(sig) do
|
||||||
|
signal_handler(sig)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
main
|
||||||
|
end
|
13
Tools/scripts/sunshar/Makefile
Normal file
13
Tools/scripts/sunshar/Makefile
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# $FreeBSD$
|
||||||
|
# $Idaemons: /home/cvs/sunshar/Makefile,v 1.1.1.1 2001/09/09 13:49:08 knu Exp $
|
||||||
|
|
||||||
|
PREFIX?= /usr/local
|
||||||
|
BINDIR= ${PREFIX}/bin
|
||||||
|
MANDIR= ${PREFIX}/man/man
|
||||||
|
|
||||||
|
SCRIPTS= sunshar.rb
|
||||||
|
MAN= sunshar.1
|
||||||
|
|
||||||
|
.PATH: ${.CURDIR}/..
|
||||||
|
|
||||||
|
.include <bsd.prog.mk>
|
56
Tools/scripts/sunshar/sunshar.1
Normal file
56
Tools/scripts/sunshar/sunshar.1
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
.\" $FreeBSD$
|
||||||
|
.\" $Idaemons: /home/cvs/sunshar/sunshar.1,v 1.2 2004/02/28 14:14:53 knu Exp $
|
||||||
|
.\"
|
||||||
|
.Dd September 9, 2001
|
||||||
|
.Dt SUNSHAR 1
|
||||||
|
.Os FreeBSD
|
||||||
|
.Sh NAME
|
||||||
|
.Nm sunshar
|
||||||
|
.Nd a secure unshar
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm
|
||||||
|
.Op Fl hfnq
|
||||||
|
.Op Fl p Ar number
|
||||||
|
.Op Ar file ...
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
The
|
||||||
|
.Nm
|
||||||
|
command extracts files from the given shell archive(s). If no file
|
||||||
|
name is given, it reads from the standard input.
|
||||||
|
.Pp
|
||||||
|
It brings you security because it never executes dangerous commands
|
||||||
|
possibly contained in a shell archive. Also, it does not overwrite
|
||||||
|
existing files unless the
|
||||||
|
.Fl f
|
||||||
|
option is specified.
|
||||||
|
.Sh OPTIONS
|
||||||
|
The following command line arguments are supported:
|
||||||
|
.Pp
|
||||||
|
.Bl -tag -width "-p number" -compact
|
||||||
|
.It Fl h
|
||||||
|
Show help and exit.
|
||||||
|
.Pp
|
||||||
|
.It Fl f
|
||||||
|
Allow overwriting existing files, and ignore errors and continue.
|
||||||
|
.Pp
|
||||||
|
.It Fl p Ar number
|
||||||
|
Strip
|
||||||
|
.Ar number
|
||||||
|
levels of path components from path names. (cf.
|
||||||
|
.Xr patch 1 's
|
||||||
|
.Fl p
|
||||||
|
option)
|
||||||
|
.Pp
|
||||||
|
.It Fl n
|
||||||
|
Do not extract anything but just show what would have been extracted.
|
||||||
|
.Pp
|
||||||
|
.It Fl q
|
||||||
|
Be quiet.
|
||||||
|
.El
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr shar 1
|
||||||
|
.Sh AUTHORS
|
||||||
|
.An Akinori MUSHA Aq knu@iDaemons.org
|
||||||
|
.Sh BUGS
|
||||||
|
.Nm
|
||||||
|
only supports shell archives made with BSD shar.
|
Loading…
Add table
Reference in a new issue