mirror of
https://git.FreeBSD.org/ports.git
synced 2024-11-25 00:51:21 +00: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
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…
Reference in New Issue
Block a user