Checking in first arpswiffer.

This commit is contained in:
Steven Bakker 2005-05-03 14:31:59 +00:00
commit 596f74efa7
16 changed files with 3073 additions and 0 deletions

25
Makefile Normal file
View File

@ -0,0 +1,25 @@
#!make
# $Id$
#
# Copyright (c) 1999-2002 Steven Bakker
# All rights reserved
#
# Notes:
# All you'll probably ever need to edit is in `config.mk'
#
# ============================================================================
TOPDIR = .
include $(TOPDIR)/config.mk
include $(TOPDIR)/rules.mk
default : all
all : sbin-all init.d-all man-all lib-all
install : sbin-install init.d-install man-install lib-install
clean : sbin-clean init.d-clean man-clean lib-clean
veryclean : sbin-veryclean init.d-veryclean man-veryclean lib-veryclean

80
config.mk Normal file
View File

@ -0,0 +1,80 @@
#!make
# $Id$
# ============================================================================
#
# Copyright (c) 1999-2003 Steven Bakker
# All rights reserved
#
# CONFIG.MK: Installation preferences
#
# ============================================================================
#
# ----------------------------------------------------------------------------
# MANDATORY CONFIG SECTION
# ----------------------------------------------------------------------------
# Where's perl on your system?
PERL = /usr/bin/perl
IFCONFIG = /sbin/ifconfig
DFL_RATE = 50
DFL_QUEUEDEPTH = 1000
DFL_ARP_AGE = 600
DFL_PENDING = 5
DFL_LOGLEVEL = info
SPONGE_OPTIONS = --age=$(DFL_ARP_AGE)
SPONGE_VAR = /var/run/arpswiffer
# ----------------------------
# --- Installation details ---
# ----------------------------
OWNER = root
GROUP = root
MODE = 644
BINMODE = 750
# ----------------------------------------------------------------------------
# END MANDATORY SECTION
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# OPTIONAL SECTION
# ----------------------------------------------------------------------------
# ------------------------------------------------------
# --- Locations for scripts, libraries, manual pages ---
# ------------------------------------------------------
# Prefix for most directories
DIRPREFIX = $(DESTDIR)/usr/local
BINPREFIX = $(DIRPREFIX)
DOCPREFIX = $(DIRPREFIX)/share
# Where to install perl scripts, jobs, library files and manual pages.
BINDIR = $(BINPREFIX)/sbin
LIBROOT = $(DIRPREFIX)/lib/site_perl
INSTLIB = $(LIBROOT)
MANDIR = $(DIRPREFIX)/man
DOCDIR = $(DOCPREFIX)/doc/$(NAME)-$(RELEASE)
# What section for the manual pages?
SECTION = 8
FILESECTION = 4
# ----------------------------------------------------------------------------
# END OPTIONAL SECTION
# ----------------------------------------------------------------------------
# Don't change this. This is for people with a brain damaged csh(1).
# Well, you may change it, as long as it points to a Bourne-like shell.
SHELL = /bin/sh
LIBDIR = $(LIBROOT)
TOOLDIR = $(TOPDIR)/tools
AUTODIR = .
CURRDIR = .

40
init.d/Makefile Normal file
View File

@ -0,0 +1,40 @@
#!make
#
# $Id$
#
# (c) Copyright 2005 Steven Bakker, AMS-IX B.V.
#
# See the LICENSE file that came with this package.
#
include ../config.mk
TOPDIR = ..
INITDIR = $(DESTDIR)/etc/init.d
INSTALLDIRS = $(DESTDIR)/etc/init.d \
$(DESTDIR)/etc/rc0.d \
$(DESTDIR)/etc/rc1.d \
$(DESTDIR)/etc/rc2.d \
$(DESTDIR)/etc/rc3.d \
$(DESTDIR)/etc/rc4.d \
$(DESTDIR)/etc/rc5.d \
$(DESTDIR)/etc/rc6.d
INSTALLINKS = \
$(DESTDIR)/etc/rc0.d/K80arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc1.d/K80arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc2.d/S30arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc3.d/S30arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc4.d/S30arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc5.d/S30arpswiffer:../init.d/arpswiffer \
$(DESTDIR)/etc/rc6.d/K80arpswiffer:../init.d/arpswiffer
TARGETS = \
arpswiffer
INSTALLFILES = \
$(INITDIR)/arpswiffer
include ../rules.mk
# E.O.F. Makefile

115
init.d/arpsponge.sh Normal file
View File

@ -0,0 +1,115 @@
#!/bin/sh
###############################################################################
# @(#) $Id$
###############################################################################
#
# Start-up script for the arpswiffer program.
#
###############################################################################
PATH=/sbin:/bin:/usr/bin:@BINDIR@
PROG=arpswiffer
SPONGE_VAR=@SPONGE_VAR@
SPONGE_OPTIONS="@SPONGE_OPTIONS@"
if test -f /etc/default/${PROG}/defaults ; then
. /etc/default/${PROG}/defaults
fi
start() {
[ "X$1" = "Xre-init" ] && re_init=true || re_init=false
SPONGES=`/bin/ls -1 /etc/default/${PROG}/eth* 2>/dev/null`
if [ -n "${SPONGES}" ]
then
echo "Starting ${PROG}(s):"
for file in ${SPONGES}
do
if=$(basename "${file}")
mkdir -p "${SPONGE_VAR}/${if}"
notify="${SPONGE_VAR}/${if}/notify"
status="${SPONGE_VAR}/${if}/status"
pidfile="${SPONGE_VAR}/${if}/pid"
# Create notification FIFO...
[ -p "${notify}" ] || /usr/bin/mkfifo --mode=644 "${notify}"
printf " %-10s " "${if}"
if ${re_init} && [ -f ${status} ]
then
init_arg="--re-init=${status}"
else
init_arg=''
fi
@BINDIR@/${PROG} ${SPONGE_OPTIONS} --daemon="${pidfile}" \
--notify="${notify}" --statusfile="${status}" \
${init_arg} \
$(/bin/cat "/etc/default/${PROG}/${if}") \
dev "${if}" 2>/dev/null \
&& echo "[Ok]" || echo "[FAILED]"
done
fi
}
stop() {
echo "Stopping ${PROG}(s):"
pidfiles=`ls ${SPONGE_VAR}/*/pid 2>/dev/null`
for pf in ${pidfiles}
do
pid=$(cat ${pf})
iface=$(basename $(dirname ${pf}))
printf " interface=%-10s pid=%-6s " ${iface} ${pid}
kill -TERM ${pid}
sleep 1
if ps -p ${pid} > /dev/null 2>&1
then
kill -KILL ${pid}
echo KILLED
else
echo terminated
fi
/bin/rm -f ${SPONGE_VAR}/${iface}/notify
done
}
status() {
if [ "X$1" = "Xre-init" ]
then
echo "Saving state:"
else
echo "Dumping status:"
fi
pidfiles=`ls ${SPONGE_VAR}/*/pid 2>/dev/null`
for pf in ${pidfiles}
do
pid=$(cat ${pf})
iface=$(basename $(dirname ${pf}))
printf " interface=%-10s pid=%-6s " ${iface} ${pid}
kill -USR1 ${pid} 2>/dev/null && echo "[Ok]" || echo "[FAILED]"
done
}
case "$1" in
start)
start
;;
restart|reload|force-reload)
status re-init
stop
start re-init
;;
status)
status
;;
stop)
stop
;;
*)
echo "Usage: $0 {start|stop|restart|reload|force-reload}"
exit 1
;;
esac
exit 0

239
lib/M6/ARP/Queue.pm Normal file
View File

@ -0,0 +1,239 @@
##############################################################################
# @(#)$Id$
##############################################################################
#
# ARP Query Timestamp Queue
#
# (c) Copyright AMS-IX B.V. 2004-2005;
#
# See the LICENSE file that came with this package.
#
# A.Vijn, S.Bakker.
#
###############################################################################
package M6::ARP::Queue;
BEGIN {
use Exporter;
our $Version = 1.01;
}
=pod
=head1 NAME
M6::ARP::Queue - ARP query timestamp queue.
=head1 SYNOPSIS
use M6::ARP::Queue;
$q = new M6::ARP::Queue($max_depth);
$q->clear($some_ip);
$q->add($some_ip, $timestamp);
while ( ! $q->is_full($some_ip) ) {
...
}
$q_depth = $q->depth($some_ip);
$q_first = $q->get($some_ip, 0);
$q_last = $q->get($some_ip, -1);
$q_per_min = $q->rate($some_ip);
$listref = $q->get_queue($some_ip);
print "timestamps: ", join(", ", @{$listref}), "\n";
=head1 DESCRIPTION
This object class is mainly used by the L<M6::ARP::Sponge|M6::ARP::Sponge>
module to store timestamps for ARP queries.
The object holds a collection of circular buffers that are accessed by
unique keys (IP address strings in the typical usage scenario). Timestamps
are added to a queue until its size reaches the maximum depth, at which
point newly added values cause the oldest values to be shifted off the
queue.
Although primarily used for storing timestamps of ARP queries
for IP addresses, it can be used for more general work as well.
Any string can be used as a queue key and arbitrary data can be
added to the queues.
Only the L</rate|rate> method makes assumptions about the data
(i.e. that they are timestamps in seconds).
=head1 CONSTRUCTOR
=over
=item X<new>B<new> ( I<MAXDEPTH> )
Create a new object instance. Each queue will have a maximum depth
of I<MAXDEPTH>. Returns a reference to the newly created object.
=cut
sub new($$) {
my $type = shift;
my $max_depth = shift;
if (ref $type) { $type = ref $type }
bless {'max_depth' => $max_depth}, $type;
}
=back
=head1 METHODS
=over
=item X<clear>B<clear> ( I<IP> )
Clear the queue for I<IP>.
=cut
sub clear($$) { delete $_[0]->{$_[1]} }
=item X<depth>B<depth> ( I<IP> )
Return the depth of the queue for I<IP>.
=cut
sub depth($$) { $_[0]->{$_[1]} ? int(@{$_[0]->{$_[1]}}) : 0 }
=item X<rate>B<rate> ( I<IP> )
Return the (average) query rate (as a real number) for I<IP> in queries
per minute.
=cut
sub rate($$) {
my $q = $_[0]->{$_[1]};
return undef if @$q < 2;
my $first = $q->[0];
my $last = $q->[$#$q];
my $time = ($first < $last) ? $last-$first : 1;
my $n = int(@$q);
return ($n / $time) * 60;
}
=item X<max_depth>B<max_depth>
Return the maximum depth of the queues.
=cut
sub max_depth($) { $_[0]->{'max_depth'} }
=item X<is_full>B<is_full> ( I<IP> )
Return whether or not the queue for I<IP> is full, i.e. is wrapping.
=cut
sub is_full($$) { $_[0]->depth($_[1]) >= $_[0]->max_depth }
=item X<add>B<add> ( I<IP>, I<TIMESTAMP> )
Add I<TIMESTAMP to the queue for I<IP>, wrapping the buffer ring if
necessary. Returns the new queue depth.
=cut
sub add($$$) {
my ($self, $ip, $val) = @_;
if ($self->depth($ip) >= $self->max_depth) {
shift @{$self->{$ip}};
}
push @{$self->{$ip}}, $val;
return int(@{$self->{$ip}});
}
=item X<get>B<get> ( I<IP> [, I<INDEX>] )
Return the data value at position I<INDEX> in the queue for I<IP>.
Zero (0) is the oldest; positive values for I<INDEX> give increasingly
more recent values. Negative numbers count from the end of the queue,
so C<-1> gives the most recently added value.
Compare:
QUEUE->get( IP, -n ) == QUEUE->get( IP, QUEUE->depth(IP) - n )
QUEUE->get( IP ) == QUEUE->get( IP, 0 );
Also:
QUEUE->get( IP, n ) == QUEUE->get-_queue( IP )->[n]
=cut
sub get($$;$) {
my ($self, $ip, $index) = @_;
$index = 0 unless defined($index);
if ($index < 0) {
$index = int(@{$self->{$ip}}) + $index;
$index = 0 if $index < 0;
}
return $self->{$ip}->[$index];
}
=item X<get_queue>B<get_queue> ( I<IP> )
Return the timestamps for I<IP>.
I<NOTE:> this is a reference to the internal list of data, so be careful
not to inadvertently modify it.
=cut
sub get_queue($$;$) {
return $self->{$ip};
}
1;
__END__
=back
=head1 EXAMPLE
use M6::ARP::Queue;
use Time::HiRes qw( usleep );
use POSIX qw( strftime );
my $some_ip = '10.1.1.1';
$q = new M6::ARP::Queue(100);
printf("filling queue for $some_ip (max %d)\n", $q->max_depth);
$q->clear($some_ip);
while (!$q->is_full($some_ip)) {
$q->add($some_ip, time);
print STDERR sprintf("\rdepth: %3d", $q->depth($some_ip));
usleep(rand(5e5));
}
printf("\rdepth: %3d\n", $q->depth($some_ip));
print strftime("first: %H:%M:%S\n", localtime($q->get($some_ip, 0)));
print strftime("last: %H:%M:%S\n", localtime($q->get($some_ip, -1)));
printf("rate: %0.2f queries/minute\n", $q->rate($some_ip));
=head1 SEE ALSO
L<perl(1)|perl>, L<M6::ARP::Sponge(3)|M6::ARP::Sponge>.
=head1 AUTHORS
Steven Bakker at AMS-IX (steven.bakker@ams-ix.net).
=cut

495
lib/M6/ARP/Sponge.pm Normal file
View File

@ -0,0 +1,495 @@
###############################################################################
# @(#)$Id$
###############################################################################
#
# ARP sponge
#
# (c) Copyright AMS-IX B.V. 2004-2005;
#
# See the LICENSE file that came with this package.
#
# A.Vijn, 2003-2004;
# S.Bakker, November 2004;
#
###############################################################################
package M6::ARP::Sponge;
use M6::ARP::Queue;
use M6::ARP::Util qw( :all );
use Net::PcapUtils;
use POSIX qw( strftime );
use NetPacket::Ethernet qw( :types );
use NetPacket::ARP qw( ARP_OPCODE_REQUEST );
use NetPacket::IP;
use Net::ARP;
use Sys::Syslog;
use Net::IPv4Addr qw( :all );
use IO::File;
BEGIN {
use Exporter;
our $Version = 1.01;
our @ISA = qw( Exporter );
my @states = qw( STATIC DEAD ALIVE PENDING );
my @log = qw( print_log print_notify );
our @EXPORT_OK = ( @states, @log );
our @EXPORT = ();
our %EXPORT_TAGS = (
'states' => \@states,
'log' => \@log,
'all' => [ @log, @states ]
);
}
use constant STATIC => -3;
use constant DEAD => -2;
use constant ALIVE => -1;
sub PENDING($;$) { 0 + $_[$#_] };
# $val = _getset($field, $sponge [, $val]);
#
# Help routine for setting/getting fields.
# When the object instance is a hash ref, simple object attributes
# are easily defined:
#
# sub myfield { _getset('myfield', @_) }
#
sub _getset($@) {
my $field = shift;
my $self = shift;
my $v = $self->{$field};
if (@_) {
$self->{$field} = shift;
}
return $v;
}
###############################################################################
#
# Object Attributes
#
###############################################################################
sub queue { $_[0]->{'queue'} }
sub device { $_[0]->{'device'} }
sub phys_device { $_[0]->{'phys_device'} }
sub syslog_ident { _getset('syslog_ident', @_) }
sub is_verbose { _getset('verbose', @_) }
sub is_dummy { _getset('dummy', @_) }
sub queuedepth { _getset('queuedepth', @_) }
sub my_ip { _getset('my_ip', @_) }
sub is_my_ip { $_[0]->{'ip_all'}->{$_[1]} }
sub my_mac { _getset('my_mac', @_) }
sub network { _getset('network', @_) }
sub netmask { _getset('netmask', @_) }
sub loglevel { _getset('loglevel', @_) }
sub max_pending { _getset('max_pending', @_) }
sub notify { _getset('notify', @_) }
sub max_rate { _getset('max_rate', @_) }
sub arp_age { _getset('arp_age', @_) }
sub gratuitous { _getset('gratuitous', @_) }
sub state_atime { $_[0]->{state_atime}->{$_[1]} }
sub set_state_atime { $_[0]->{state_atime}->{$_[1]} = $_[2] }
sub state_mtime { $_[0]->{state_mtime}->{$_[1]} }
sub set_state_mtime { $_[0]->{state_mtime}->{$_[1]} = $_[2] }
sub state_table { $_[0]->{state} }
sub get_state { $_[0]->{state}->{$_[1]} }
sub set_state {
$_[0]->{state_mtime}->{$_[1]} = $_[0]->{state_atime}->{$_[1]} = time;
$_[0]->{state}->{$_[1]} = $_[2];
}
###############################################################################
# $sponge->DESTROY
#
# Destructor. Called by Perl's garbage collection.
###############################################################################
sub DESTROY {
my $self = shift;
if ($self->notify) {
$self->notify->close;
}
}
###############################################################################
# $sponge = new M6::ARP::Sponge(ARG => VAL ...)
#
# Create a new Sponge object.
#
###############################################################################
sub new {
my $type = shift;
my $self = {};
while (@_ >= 2) {
my $k = shift @_;
my $v = shift @_;
$self->{lc $k} = $v;
}
bless $self, $type;
$self->{queuedepth} = '@DFL_QUEUEDEPTH@' unless $self->queuedepth;
unless (length $self->syslog_ident) {
my ($prog) = $0 =~ m|([^/]+)$|;
$self->syslog_ident($prog);
}
$self->{state} = {};
$self->{state_mtime} = {};
$self->{state_atime} = {};
$self->{queue} = new M6::ARP::Queue($self->queuedepth);
$self->my_ip( $self->get_ip );
$self->my_mac( $self->get_mac );
$self->{'ip_all'} = { map { $_ => 1 } $self->get_ip_all };
$self->loglevel('info') unless length $self->loglevel;
$self->{'arp_table'} = {
$self->my_ip => [ $self->my_mac, time ]
};
($self->{'phys_device'}) = split(/:/, $self->{'device'});
if ($self->is_verbose) {
$self->verbose(1, "Device: ", $self->device, "\n");
$self->verbose(1, "Device: ", $self->phys_device, "\n");
$self->verbose(1, "MAC: ", $self->my_mac, "\n");
$self->verbose(1, "IP: ", $self->my_ip, "\n");
}
return $self;
}
###############################################################################
# $table = $sponge->arp_table;
# ($mac, $time) = $sponge->arp_table($ip);
# ($mac, $time) = $sponge->arp_table($ip, $mac [, $time]);
#
# Perform a ARP table lookup, or update the ARP table.
#
###############################################################################
sub arp_table($;$$$) {
my $self = shift;
return $self->{'arp_table'} unless @_;
my $ip = shift;
if (@_) {
my $mac = shift;
my $time = @_ ? shift : time;
$self->{'arp_table'}->{$ip} = [ $mac, $time ];
}
return @{$self->{'arp_table'}->{$ip}};
}
###############################################################################
# $mac = $sponge->get_mac;
# $mac = $sponge->get_mac($device);
# $mac = get_mac($device);
#
# Return MAC address for device $device.
#
###############################################################################
sub get_mac($;$) {
my $dev = pop @_;
if (ref $dev) { $dev = $dev->device }
my $mac;
Net::ARP::get_mac($dev, $mac);
return mac2mac($mac);
}
###############################################################################
# @ip = $sponge->get_ip_all;
#
# Return all IP addresses for physical device $device. This includes all
# addresses configured on "sub" interfaces.
#
###############################################################################
sub get_ip_all {
my @ip;
open(IFCONFIG, 'ifconfig -a 2>/dev/null|');
local($_);
while (<IFCONFIG>) {
if (/^.*inet addr:(\S+)/) {
push @ip, $1;
}
}
close IFCONFIG;
return @ip;
}
###############################################################################
# $ip = $sponge->get_ip;
# $ip = $sponge->get_ip($device);
# $ip = get_ip($device);
#
# Return IP address for device $device, or '0.0.0.0' if none.
#
###############################################################################
sub get_ip($;$) {
my $dev = pop @_;
if (ref $dev) { $dev = $dev->device }
my $ip = `ifconfig $dev 2>/dev/null`;
unless ($ip =~ s/^.*inet addr:(\S+).*$/$1/s) {
$ip = '0.0.0.0';
}
return $ip;
}
###############################################################################
# $bool = $sponge->is_my_network($target_ip)
#
# Returns whether or not $target_ip is in the monitored
# network range(s).
#
###############################################################################
sub is_my_network($$) {
my ($self, $target_ip) = @_;
return ipv4_in_network($self->network, $self->netmask, $target_ip);
}
###############################################################################
# $state = $sponge->set_pending($target_ip, $n);
#
# Set $target_ip's state to PENDING "$n". Returns new state.
#
###############################################################################
sub set_pending($$;$) {
my ($self, $target_ip, $n) = @_;
$self->set_state($target_ip, PENDING($n));
}
###############################################################################
# $state = $sponge->incr_pending($target_ip);
#
# Increment $target_ip's PENDING state. Returns new state.
#
###############################################################################
sub incr_pending($$) {
my ($self, $target_ip) = @_;
$self->set_state($target_ip, $self->get_state($target_ip)+1);
}
###############################################################################
# $sponge->send_probe($target_ip);
#
# Send a (probe) ARP "WHO HAS $target_ip". This prevents us from
# erroneously sponging when there's a cretin sending ARP floods.
#
###############################################################################
sub send_probe($$) {
my ($self, $target_ip) = @_;
$self->verbose(2, "Probing [dev=", $self->phys_device, "]: $target_ip\n");
$self->set_state_atime($target_ip, time);
return if $self->is_dummy;
Net::ARP::send_packet($self->phys_device,
$self->my_ip, $target_ip,
$self->my_mac, 'ff:ff:ff:ff:ff:ff',
'request'
);
}
###############################################################################
# $sponge->gratuitous_arp($ip);
#
# Send a (sponge) ARP WHO HAS $ip TELL $ip".
#
###############################################################################
sub gratuitous_arp($$) {
my ($self, $ip) = @_;
$self->verbose(1, "Gratuitous ARP [dev=", $self->phys_device, "]: $ip\n");
$self->set_state_atime($ip, time);
return if $self->is_dummy;
Net::ARP::send_packet($self->phys_device,
$ip, $ip,
$self->my_mac, 'ff:ff:ff:ff:ff:ff',
'request'
);
}
###############################################################################
# $sponge->send_reply($src_ip, $arp_obj);
#
# Send a (sponge) ARP "$src_ip IS AT" in reply to the $arp_obj request.
#
###############################################################################
sub send_reply($$$) {
my ($self, $src_ip, $arp_obj) = @_;
$self->set_state_atime($src_ip, time);
# Figure out where to send the reply...
my $dst_mac = hex2mac($arp_obj->{sha});
my $dst_ip = hex2ip($arp_obj->{spa});
$self->verbose(1, "$src_ip: sponge reply to $dst_ip\@$dst_mac\n");
return if $self->is_dummy;
Net::ARP::send_packet($self->phys_device, $src_ip, $dst_ip,
$self->my_mac, $dst_mac, 'reply'
);
}
###############################################################################
# $sponge->set_dead($target_ip);
#
# Set $target_ip's state to DEAD (i.e. "sponged").
#
###############################################################################
sub set_dead($$) {
my ($self, $ip) = @_;
my $rate = $self->queue->rate($ip);
$self->print_log("ARP-rate for %s is %0.1f q/min", $ip, $rate);
$self->print_log("Sponging: %s", $ip);
$self->print_notify("action=sponge;ip=%s;mac=%s", $ip, $self->my_mac);
$self->gratuitous_arp($ip) if $self->gratuitous;
$self->set_state($ip, DEAD);
# This is the place where we could send a gratuitous ARP for
# the sponged address to shut up all other queriers.
}
###############################################################################
# set_alive($data, $target_ip, $target_mac);
#
# Unsponge the $target_ip, which is now seen from $target_mac.
# Update ARP cache and print appropriate notifications.
#
###############################################################################
sub set_alive($$$) {
my ($self, $ip, $mac) = @_;
return unless $self->is_my_network($ip);
if ($self->get_state($ip) == DEAD) {
$self->print_log("Unsponging: %s [found at %s]", $ip, $mac);
$self->print_notify("action=unsponge;ip=%s;mac=%s", $ip, $mac);
}
elsif ($self->queue->depth($ip) > 0) {
$self->verbose(1, "Clearing: $ip [found at $mac]\n");
$self->print_notify("action=clear;ip=%s;mac=%s", $ip, $mac);
}
$self->queue->clear($ip);
$self->set_state($ip, ALIVE);
my @arp = $self->arp_table($ip);
if (!@arp) {
$self->print_notify("action=learn;ip=%s;mac=%s", $ip, $mac);
}
elsif ($arp[0] ne $mac) {
$self->print_notify("action=flip;ip=%s;mac=%s", $ip, $mac);
}
elsif (time - $arp[1] > $self->arp_age) {
$self->print_notify("action=refresh;ip=%s;mac=%s",
$ip, $mac);
}
$self->arp_table($ip, $mac, time);
}
###############################################################################
# $sponge->verbose($level, $arg, ...);
# verbose($level, $arg, ...);
#
# Print the arguments to STDOUT if verbosity is at least $level.
#
###############################################################################
sub verbose($$;@) {
my ($self, $verbose);
if (UNIVERSAL::isa($_[0], 'M6::ARP::Sponge')) {
$self = shift;
$verbose = $self->is_verbose;
}
$verbose = $::opt_verbose unless $verbose;
my $level = shift;
if ($verbose >= $level) {
print STDOUT strftime("%Y-%m-%d %H:%M:%S ", localtime(time)), @_;
}
}
###############################################################################
# $sponge->print_log_level($level, $format, ...);
# print_log_level($level, $format, ...);
###############################################################################
sub print_log_level {
my ($self, $syslog);
if (UNIVERSAL::isa($_[0], 'M6::ARP::Sponge')) {
$self = shift;
$syslog = $self->syslog_ident;
}
$syslog = $0 unless length $syslog;
my ($level, $format, @args) = @_;
if ($self->is_dummy || $self->is_verbose) {
print STDOUT strftime("%Y-%m-%d %H:%M:%S ", localtime(time));
print STDOUT $syslog, "[$$]: ";
chomp(my $msg = sprintf($format, @args));
print STDOUT $msg, "\n";
}
else {
openlog($syslog, 'cons,pid', 'user');
syslog($level, $format, @args);
closelog;
}
}
###############################################################################
# $sponge->print_log($format, ...);
#
# Log $format, ... to syslog. Syntax is identical to that of printf().
# Prints to STDOUT if verbose or dummy.
###############################################################################
sub print_log {
my ($self, $format, @args) = @_;
$self->print_log_level($self->loglevel, $format, @args);
}
###############################################################################
# $sponge->print_notify($format, ...);
# print_notify($fh, $format, ...);
#
# Notify of sponge actions on the notify handle.
###############################################################################
sub print_notify($$;@) {
my ($self, $fh);
if (UNIVERSAL::isa($_[0], 'M6::ARP::Sponge')) {
$self = shift;
$fh = $self->notify;
}
elsif (UNIVERSAL::isa($_[0], 'IO::Handle')) {
$fh = shift;
}
return unless defined $fh;
my $format = shift @_;
$fh->print(int(time), ";id=",
$self->syslog_ident, ";", sprintf($format, @_), "\n");
}
1;

157
lib/M6/ARP/Table.pm Normal file
View File

@ -0,0 +1,157 @@
##############################################################################
# @(#)$Id$
##############################################################################
#
# ARP Table
#
# (c) Copyright AMS-IX B.V. 2004-2005;
#
# See the LICENSE file that came with this package.
#
# S.Bakker, 2005
#
###############################################################################
package M6::ARP::Table;
use Time::HiRes qw( time );
BEGIN {
use Exporter;
our $Version = 1.01;
}
=pod
=head1 NAME
M6::ARP::Table - keep a table of ARP entries
=head1 SYNOPSIS
use M6::ARP::Table;
$table = new M6::ARP::Table;
$table->clear($some_ip);
$table->add($some_ip, $some_mac);
$mac = $table->arp($some_ip);
$stamp = $table->mtime($some_ip);
@iplist = $table->rarp($mac);
@iplist = $table->ip_list;
@maclist = $table->mac_list;
=head1 DESCRIPTION
This object class can be used by network monitoring processes to keep
track of IP to MAC mappings.
=head1 CONSTRUCTOR
=over
=item X<new>B<new>
Create a new object instance and return a reference to it.
=cut
sub new($$) {
my $type = shift;
my $max_depth = shift;
if (ref $type) { $type = ref $type }
bless { arp => {}, rarp => {} }, $type;
}
=back
=head1 METHODS
=over
=item X<clear>B<clear> ( I<IP> )
Clear the ARP table for I<IP>.
=cut
sub clear($$) {
my ($self, $ip) = @_;
if (my $mac = $self->arp($ip)) {
delete $self->{rarp}->{$mac}->{$ip};
}
delete $self->{arp}->{$ip};
}
=item X<arp>B<arp> ( I<IP> )
Return the MAC address for I<IP>. Returns C<undef> if there is no
entry for I<IP>.
=cut
sub arp($$) { $_[0]->{'arp'}->{$_[1]} }
=item X<rarp>B<rarp> ( I<MAC> )
Return a sorted list of IP addresses that are mapped to I<MAC>.
=cut
sub rarp($$) { sort { ip_sort($a, $b) } keys %{$_[0]->{'rarp'}->{$_[1]}} }
=item X<ip_list>B<ip_list>
Return a sorted list of IP addresses that are present in the ARP table.
=cut
sub ip_list($) { sort { ip_sort($a, $b) } keys %{$_[0]->{'arp'}} }
=item X<mac_list>B<mac_list>
Return a sorted list of MAC addresses that are present in the ARP table.
=cut
sub mac_list($) { sort { ip_sort($a, $b) } keys %{$_[0]->{'rarp'}} }
=item X<add>B<add> ( I<IP>, I<MAC> [, I<TIMESTAMP>] )
Add I<IP> to I<MAC> mapping to the table. If I<TIMESTAMP> is given, use
it for the entry's timestamp, otherwise use the current time.
Returns the timestamp.
=cut
sub add($$$;$) {
my ($self, $ip, $mac, $timestamp) = @_;
$timestamp = time unless defined($timestamp);
$self->clear($ip);
$self->{'arp'}->{$ip} = $mac;
$self->{'rarp'}->{$mac}->{$ip} = $timestamp;
return $timestamp;
}
1;
__END__
=back
=head1 EXAMPLE
See the L</SYNOPSIS> section.
=head1 SEE ALSO
L<perl(1)|perl>, L<M6::ARP::Sponge(3)|M6::ARP::Sponge>.
=head1 AUTHORS
Steven Bakker at AMS-IX (steven.bakker@ams-ix.net).
=cut

195
lib/M6/ARP/Util.pm Normal file
View File

@ -0,0 +1,195 @@
##############################################################################
# @(#)$Id$
##############################################################################
#
# ARP Stuff Utility routines
#
# (c) Copyright AMS-IX B.V. 2004-2005;
#
# See the LICENSE file that came with this package.
#
# S.Bakker.
#
###############################################################################
package M6::ARP::Util;
BEGIN {
use Exporter;
our $Version = 1.01;
our @ISA = qw( Exporter );
our @EXPORT_OK = qw( int2ip ip2int hex2ip ip2hex hex2mac mac2hex mac2mac );
our @EXPORT = ();
our %EXPORT_TAGS = (
'all' => [ @EXPORT_OK ]
);
}
=pod
=head1 NAME
M6::ARP::Util - IP/MAC utility routines
=head1 SYNOPSIS
use M6::ARP::Util qw( :all );
$ip = int2ip( $num );
$num = ip2int( $ip );
$ip = hex2ip( $hex );
$hex = ip2hex( $ip );
$mac = hex2mac( $hex );
$hex = mac2hex( $mac );
$mac = mac2mac( $mac );
=head1 DESCRIPTION
This module defines a number of routines to convert IP and MAC
representations to and from various formats.
=head1 FUNCTIONS
=over
=cut
###############################################################################
=item X<int2ip>B<int2ip> ( I<num> )
Convert a (long) integer to a dotted decimal IP address. Return the
dotted decimal string.
Example: int2ip(3250751620) returns "193.194.136.132".
=cut
sub int2ip($) {
hex2ip(sprintf("%08x", shift @_));
};
###############################################################################
=item X<ip2int>B<ip2int> ( I<IPSTRING> )
Dotted decimal IPv4 address to integer representation.
Example: ip2int("193.194.136.132") returns "3250751620".
=cut
sub ip2int($) {
hex(ip2hex(shift @_));
};
###############################################################################
=item X<hex2ip>B<hex2ip> ( I<HEXSTRING> )
Hexadecimal IPv4 address to dotted decimal representation.
Example: hex2ip("c1c28884") returns "193.194.136.132".
=cut
sub hex2ip($) {
my $hex = shift;
$hex =~ /(..)(..)(..)(..)/;
my $ip = sprintf("%d.%d.%d.%d", hex($1), hex($2), hex($3), hex($4));
return $ip;
};
###############################################################################
=item X<ip2hex>B<ip2hex> ( I<IPSTRING> )
Dotted decimal IPv4 address to hex representation.
Example: ip2hex("193.194.136.132")
returns "c1c28884".
=cut
sub ip2hex($) {
return sprintf("%02x%02x%02x%02x", split(/\./, shift));
};
###############################################################################
=item X<hex2mac>B<hex2mac> ( I<HEXSTRING> )
Hexadecimal MAC address to colon-separated hex representation.
Example: hex2mac("a1b20304e5f6")
returns "a1:b2:03:04:e5:f6"
=cut
sub hex2mac($) {
my $hex = substr("000000000000".(shift @_), -12);
$hex =~ /(..)(..)(..)(..)(..)(..)/;
return sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
hex($1), hex($2), hex($3), hex($4), hex($5), hex($6));
};
###############################################################################
=item X<mac2hex>B<mac2hex> ( I<macstring> )
Any MAC address to hex representation.
Example:
mac2hex("a1:b2:3:4:e5:f6")
returns "a1b20304e5f6".
=cut
sub mac2hex($) {
my @mac = split(/[\s\.\-:\-]/, shift);
return undef if 12 % int(@mac);
my $digits = int(12 / int(@mac));
my $hex;
my $pref = "0" x $digits;
foreach (@mac) { $hex .= substr($pref.$_, -$digits) }
return lc $hex;
};
###############################################################################
=item X<mac2mac>B<mac2mac> ( I<MACSTRING> )
Any MAC address to colon-separated hex representation (6 groups of 2 digits).
Example: mac2mac("a1b2.304.e5f6")
returns "a1:b2:03:04:e5:f6"
=cut
sub mac2mac($) {
hex2mac(mac2hex($_[0]));
}
1;
__END__
=back
=head1 EXAMPLE
See the L</SYNOPSIS> section.
=head1 SEE ALSO
L<perl(1)|perl>, L<M6::ARP::Sponge(3)|M6::ARP::Sponge>.
=head1 AUTHORS
Steven Bakker at AMS-IX (steven.bakker@ams-ix.net).
=cut

28
man/Makefile Normal file
View File

@ -0,0 +1,28 @@
#!make
#
# $Id$
#
# (c) Copyright 2005 Steven Bakker, AMS-IX B.V.
#
# See the LICENSE file that came with this package.
#
include ../config.mk
TOPDIR = ..
SECTION = 8
TARGETS = \
arpswiffer.pod \
arpswiffer.$(SECTION)
INSTALLFILES = \
$(MANDIR)/man$(SECTION)/arpswiffer.$(SECTION)
include ../rules.mk
arpswiffer.pod: ../sbin/arpswiffer
$(RM) -f arpswiffer.pod
ln -s ../sbin/arpswiffer arpswiffer.pod
# E.O.F. Makefile

257
rules.mk Normal file
View File

@ -0,0 +1,257 @@
#
#!make
# $Id$
#
# Copyright (c) 2002 Steven Bakker
# All rights reserved
default : all
RM = /bin/rm -f
MV = /bin/mv
RELEASE = 3.3
NAME = arpswiffer
PACKAGE = $(NAME)-$(RELEASE)
TOOLDIR = $(TOPDIR)/tools
INSTALL_LOG = $(TOPDIR)/installed.log
INSTALLPROG = $(TOOLDIR)/bsdinst -c -l $(INSTALL_LOG)
INSTALL = $(INSTALLPROG) -o $(OWNER) -g $(GROUP) -m $(MODE)
BININSTALL = $(INSTALLPROG) -o $(OWNER) -g $(GROUP) -m $(BINMODE)
MKDIR = $(TOOLDIR)/mkinstalldirs
RMDIR = $(TOOLDIR)/rminstalldirs
#
# Substitute configuration variables in files.
#
perlit= $(PERL) -p -e \
"s!\@LIBDIR@!$(LIBDIR)!g; \
s!\@BINDIR@!$(BINDIR)!g; \
s!\@DFL_PATH@!$(DFL_PATH)!g; \
\
s!\@NAME@!$(NAME)!g; \
s!\@UNAME@!\U$(NAME)\E!g; \
s!\@Uname@!\u$(NAME)!g; \
\
s!\@OWNER@!$(OWNER)!g; \
s!\@GROUP@!$(GROUP)!g; \
\
s!\@SECTION@!\U$(SECTION)\E!g; \
s!\@USECTION@!\U$(SECTION)\E!g; \
s!\@FILESECTION@!$(FILESECTION)!g; \
s!\@UFILESECTION@!\U$(FILESECTION)\E!g; \
\
s!\@RELEASE@!$(RELEASE)!g; \
s!\@SHELL@!$(SHELL)!g; \
s!\@PERL@!$(PERL)!g; \
\
s!\@SPONGE_VAR@!$(SPONGE_VAR)!g; \
s!\@SPONGE_OPTIONS@!$(SPONGE_OPTIONS)!g; \
\
s!\@IFCONFIG@!$(IFCONFIG)!g; \
s!\@DFL_RATE@!$(DFL_RATE)!g; \
s!\@DFL_ARP_AGE@!$(DFL_ARP_AGE)!g; \
s!\@DFL_QUEUEDEPTH@!$(DFL_QUEUEDEPTH)!g; \
s!\@DFL_PENDING@!$(DFL_PENDING)!g; \
s!\@DFL_LOGLEVEL@!$(DFL_LOGLEVEL)!g; \
"
.SUFFIXES: .al .pm .pmrsc .pl \
.src \
.sample \
.sh \
.txt .ps \
.$(SECTION) .pod .man .txt
% : %.sh Makefile
@echo building $@ from $<
@$(perlit) $< > $@
@chmod 755 $@
%.sample : %.sample.src Makefile
@echo building $@ from $<
@$(perlit) $< > $@
@chmod 644 $@
% : %.src Makefile
@echo building $@ from $<
@$(perlit) $< > $@
@chmod 644 $@
% : %.pl Makefile
@ echo building $@ from $<
@ $(perlit) $< > $@
@ chmod 755 $@
@ $(PERL) -wc $@ || $(RM) $@
%.$(SECTION) : %.pod
@echo building $@ from $<
@PERLLIB=$$PERLLIB:$(TOPDIR)/lib; export PERLLIB; \
pod2man \
--release="$(NAME)-$(RELEASE)" \
--date="`date`" \
--center="AMS-IX Management Utilities" \
--section=$(SECTION) \
--name="`echo $* | sed -e 's/\.\./::/g'`" \
$< > $@
%.html : %.pod
@echo building $@ from $<
@PERLLIB=$$PERLLIB:$(TOPDIR)/lib; export PERLLIB; \
$(TOOLDIR)/pod2html \
--name="`echo $* | sed -e 's/\.\./::/g'`" \
$< > $@
%.txt : %.$(SECTION)
@echo building $@ from $<
@$(perlit) $< | gnroff -mgan > $@
%.ps : %.$(SECTION)
@echo building $@ from $<
@$(perlit) $< | groff -Tps -mgan > $@
$(INITDIR)/% : %
@echo installing executable $< in $(INITDIR)
$(MKDIR) $(INITDIR) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
$(BININSTALL) $< $@
$(PERL) -pi -e 's|^(#!/.*) -I../lib|$$1|' $@
$(BINDIR)/% : %
@echo installing executable $< in $(BINDIR)
$(MKDIR) $(BINDIR) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
$(BININSTALL) $< $@
$(PERL) -pi -e 's|^(#!/.*) -I../lib|$$1|' $@
$(MANDIR)/man$(SECTION)/% : %
@echo installing $< in $(MANDIR)/man$(SECTION)
@$(MKDIR) $(MANDIR)/man$(SECTION) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
@$(INSTALL) $< $@
$(INSTDIR1)/% : %
@echo installing $< in $(INSTDIR1)
@$(MKDIR) $(INSTDIR1) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
@$(INSTALL) $< $@
$(INSTDIR2)/% : %
@echo installing $< in $(INSTDIR2)
@$(MKDIR) $(INSTDIR2) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
@$(INSTALL) $< $@
$(INSTDIR3)/% : %
@echo installing $< in $(INSTDIR3)
@$(MKDIR) $(INSTDIR3) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
@$(INSTALL) $< $@
$(INSTALLDIR)/% : %
@echo installing $< in $(INSTALLDIR)
@$(MKDIR) $(INSTALLDIR) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
@$(INSTALL) $< $@
%.sample : %
@echo building $@ from $<
@$(perlit) $< > $@
@chmod 644 $@
auto/$(AUTO)/%/autosplit.ix : $(AUTO)/%.pm
@echo autosplit $<;
@PERLLIB=$$PERLLIB:$(TOPDIR)/lib; export PERLLIB; \
$(TOOLDIR)/autosplit ./auto $<
auto/$(AUTO1)/%/autosplit.ix : $(AUTO1)/%.pm
@echo autosplit $<;
@PERLLIB=$$PERLLIB:$(TOPDIR)/lib; export PERLLIB; \
$(TOOLDIR)/autosplit ./auto $<
%-all : ; cd $* ; make ${MFLAGS} all
%-install : ; cd $* ; make ${MFLAGS} install
%-uninstall : ; cd $* ; make ${MFLAGS} uninstall
%-autosplit : ; cd $* ; make ${MFLAGS} autosplit
%-clean : ; cd $* ; make ${MFLAGS} clean
all : $(TARGETS)
install : all installdirs $(INSTALLFILES) install-links post-install
installdirs :
@echo "Checking/creating installation directories..."
@echo $(INSTALLDIRS)
@$(MKDIR) $(INSTALLDIRS) 2>&1 | sed -e 's/^mkdir //' >> $(INSTALL_LOG)
post-install :
@$(RM) $(INSTALL_LOG).tmp; \
sort -ru $(INSTALL_LOG) > $(INSTALL_LOG).tmp; \
$(MV) $(INSTALL_LOG).tmp $(INSTALL_LOG)
uninstall :
@echo "Removing installed files:" ; \
files=$(INSTALLFILES); \
if [ -f $(INSTALL_LOG) ]; then \
files="$$files `cat $(INSTALL_LOG)`"; \
fi; \
echo '** Warning: will remove the following files:'; \
echo $$files | $(PERL) -p -e '\
@x = split(" ", $$_); \
$$_ = " ".join("\n ", @x)."\n"'; \
if [ `echo "\c" | wc -c` -gt 0 ]; then \
echo -n "Are you sure [yn] y"; \
else \
echo "Are you sure [ny] n\c"; \
fi; \
read ans; \
case "$$ans" in \
y*|Y*) echo "Removing ..."; \
$(RM) $$files >/dev/null 2>&1; \
$(RMDIR) $$files >/dev/null 2>&1; \
$(RM) $(INSTALL_LOG); \
echo "Done"; \
true;; \
*) false;; \
esac
x-uninstall : ; $(RM) $(INSTALLFILES)
clean : ; @echo cleaning up
@$(RM) $(TARGETS) core 2>/dev/null \
$(NAME)_*.deb; \
true
install-links:
@for link in ._no $(INSTALLLINKS) $(INSTALLINKS); do \
[ $$link = ._no ] && continue; \
linkname=`echo $$link | cut -f1 -d:`; \
fname=`echo $$link | cut -f2 -d:`; \
if [ ! -f $$linkname ] || [ -h $$linkname ]; then \
target=`/bin/ls -l $$linkname 2>/dev/null | sed -e 's|^.*-> ||'`; \
if [ X$$target != X$$fname ]; then \
$(RM) $$linkname; \
ln -s $$fname $$linkname; \
echo $$linkname; \
fi; \
fi; \
done
veryclean : clean
@if [ -d ./SCCS ]; then sccs clean; fi
_debtemp := /tmp/deb.$(NAME).$(shell echo $$RANDOM)
dpkg:
mkdir -p $(_debtemp)
cp -rp . $(_debtemp)/$(NAME)-$(RELEASE)
cd $(_debtemp)/$(NAME)-$(RELEASE); \
(fakeroot debian/rules binary || true)
ls $(_debtemp)/$(NAME)_*.deb >/dev/null 2>&1; \
[ $$? = 0 ] && mv $(_debtemp)/$(NAME)_*.deb .
$(RM) -rf $(_debtemp)
#$(RM) -rf debian *.deb
#dh_make --single; \
#
# %: define.h %.c
# $@: target (wonkie)
# $^: dependencies (define.h wonkie.c)
# $<: primary source file (define.h)
# $?: out of date dependency (wonkie.c)
# $*: portion that matched the "%" (wonkie)

23
sbin/Makefile Normal file
View File

@ -0,0 +1,23 @@
#!make
#
# $Id$
#
# (c) Copyright 2005 Steven Bakker, AMS-IX B.V.
#
# See the LICENSE file that came with this package.
#
include ../config.mk
TOPDIR = ..
INSTALLDIRS = $(BINDIR)
TARGETS = \
arpswiffer
INSTALLFILES = \
$(BINDIR)/arpswiffer
include ../rules.mk
# E.O.F. Makefile

1140
sbin/arpsponge.pl Normal file

File diff suppressed because it is too large Load Diff

18
t/dosponge Normal file
View File

@ -0,0 +1,18 @@
#IPADDR/PREFIXLEN dev IFNAME
rm -f swif.out swif.notify swif.status
mkfifo swif.notify
#./arpswiffer 10.1.1.0/25 dev eth0:1 \
#../sbin/arpswiffer 193.194.136.128/25 dev eth0 \
arpswiffer 193.194.136.128/25 dev eth0 \
--verbose=1 \
--queuedepth=20 \
--sweep=900/3600 \
--rate=50 \
--pending=3 \
--notify=swif.notify \
--statusfile=swif.status \
2>&1 | es -a -s swif.out &
# --dummy \
tail -f swif.out

146
tools/bsdinst Executable file
View File

@ -0,0 +1,146 @@
#!/bin/sh
#
# $Id$
#
# bsdinst.sh: BSD-like install program
#
# Not all BSD install options are supported, and one extra is added:
#
# -l logfile
prog=`basename $0`
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/ucb
export PATH
usage() {
echo "$@" >&2
echo "usage: $prog [options] file ... destination" >&2
echo "options: [-cs] [-l logfile] [-g group] [-m mode] [-o owner]" >&2
exit 1;
}
verbose=false
go_on=true
copy=true
strip=false
mode=755
logfile=''
[ `whoami` = root ] && root=true || root=false
if $root; then
owner=root
group=staff
fi
while [ $# -gt 0 ] && $go_on
do
case X:$1 in
X:-s) shift
strip=true ;;
X:-c) shift
copy=true ;;
X:-cs |\
X:-sc) shift
copy=true
strip=true ;;
X:-l) shift
logfile=$1
shift ;;
X:-g) shift
if $root; then
group=$1
elif $verbose; then
echo "warning: \"-g\" flag ignored (not super-user)" >&2
fi
shift ;;
X:-m) shift
mode=$1
shift;;
X:-d) usage "\"-d\" flag is not supported (sorry)" ;;
X:-v) shift
verbose=true ;;
X:-o) shift
if $root; then
owner=$1
elif $verbose; then
echo "warning: \"-o\" flag ignored (not super-user)" >&2
fi
shift ;;
X:---) shift; go_on=false;;
X:-*) usage "unknown option \"$1\"" ;;
X:*) go_on=false ;;
esac
done
if [ $# -lt 2 ]; then
usage "too few arguments"
elif [ $# -gt 2 ]; then
multiple_src=true
else
multiple_src=false
fi
while [ $# -gt 1 ]
do
files="$files $1"
shift
done
destination=$1
[ -d "$destination" ] && dst_is_a_file=false || dst_is_a_file=true
if $multiple_src && $dst_is_a_file
then
usage
fi
set $files
$copy && mvcp=cp || mvcp=mv
for file
do
$dst_is_a_file && dst_file=$destination || \
dst_file=$destination/`basename $file`
$verbose && echo + $mvcp $file $dst_file
rm -f $dst_file
$mvcp $file $dst_file || exit 1
if [ -n "$logfile" ]; then
echo $dst_file >> $logfile
fi
if [ -n "$mode" ]; then
chmod 600 $dst_file
$verbose && echo + chmod $mode $dst_file
chmod $mode $dst_file > /dev/null 2>&1
[ $? = 0 ] || \
echo "$dst_file: could not chmod (ignored)" >&2
fi
if $strip; then
$verbose && echo + strip $dst_file
strip $dst_file >/dev/null 2>&1
[ $? = 0 ] || \
echo "$dst_file: could not strip (ignored)" >&2
fi
if [ -n "$owner" ]; then
$verbose && echo + chown $owner $dst_file
chown $owner $dst_file >/dev/null 2>&1
[ $? = 0 ] || \
echo "$dst_file: could not chown (ignored)" >&2
fi
if [ -n "$group" ]; then
$verbose && echo + chgrp $group $dst_file
chgrp $group $dst_file >/dev/null 2>&1
[ $? = 0 ] || \
echo "$dst_file: could not chgrp (ignored)" >&2
fi
done

40
tools/mkinstalldirs Executable file
View File

@ -0,0 +1,40 @@
#! /bin/sh
# @(#) $Id$
#
# mkinstalldirs --- make directory hierarchy
# Author: Noah Friedman <friedman@prep.ai.mit.edu>
# Created: 1993-05-16
# LICENSE: Public domain
errstatus=0
for file
do
set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
shift
pathcomp=
for d
do
pathcomp="$pathcomp$d"
case "$pathcomp" in
-* ) pathcomp=./$pathcomp ;;
esac
if test ! -d "$pathcomp"; then
echo "mkdir $pathcomp" 1>&2
mkdir "$pathcomp" || lasterr=$?
if test ! -d "$pathcomp"; then
errstatus=$lasterr
fi
fi
pathcomp="$pathcomp/"
done
done
exit $errstatus
# mkinstalldirs ends here

75
tools/rminstalldirs Executable file
View File

@ -0,0 +1,75 @@
#!/bin/sh
#
# $Id$
#
# rminstalldirs --- remove directory hierarchy
#
# Works (almost) similar to "rmdir -ps", except that the whole path
# needn't exist. For example,
#
# rminstalldirs /dir1/dir2/dir3/file1
#
# - will still remove the whole tree if "file1" does not exist. Of course
# it will not remove any directory that is not empty.
#
# Author: Steven Bakker <steven@monkey-mind.net>
# Created: 1998-06-24
PATH=/sbin:/bin:/usr/bin:/usr/sbin
OLDIFS="$IFS"
do_rmdir() {
if [ `echo "$1" | sed -e 's:^/::g'` = "$1" ]
then
dir="`pwd`/$1"
else
dir=$1
fi
shift
cd /
path=''
IFS=/
set - $dir
IFS="$OLDIFS"
# Go down the path as far as possible, keeping track of
# the reverse path back up ($path).
for i in $*
do
[ -d $i ] || break
cd $i
path="$i $path"
done
# Go up one directory, so we can "rmdir" it...
cd ..
# Now travel back up the tree, removing subdirs as we go...
set - $path
goon=true
while $goon && [ $# -gt 0 ]
do
d=$1; shift
if [ $d = . -o $d = .. ]
then
# "." and ".." references in the path act as sentinels.
goon=false
else
if rmdir $d >/dev/null 2>&1
then
echo rmdir `pwd`/$d
cd ..
else
# Failed "rmdir"; give up.
goon=false
fi
fi
done
}
for dir in "$@"
do
do_rmdir $dir
done