#!/usr/bin/perl -w # # ifc - Interface configuration script # # Author: Jochen Wiedmann # Am Eisteich 9 # 72555 Metzingen # Germany # # E-Mail: +49 7123 14887 # my $VERSION = "ifc, Version 20-May-1999, by Jochen Wiedmann"; my $INTERFACE = "eth0"; my $IP = "192.168.1.2"; my $NETMASK = "255.255.255.0"; my $IFCONFIG = "/sbin/ifconfig"; my $ROUTE = "/sbin/route"; my $GATEWAY = "192.168.1.1"; my $NAMESERVER = "192.168.1.1"; use strict; use Getopt::Long (); use Data::Dumper (); use Socket (); =pod =head1 NAME ifc - Interface Configuration Script =head1 SYNOPSIS # Configure an interface in an interactive dialog, optionally # creating a new configuration ifc # Configure an interface using the builtin ifc =head1 DESCRIPTION This script is for you, laptop users, who are frequently attaching your machine into a different network. No longer entering "ifconfig" and "route" commands, simply entering IP addresses and related data in an interacetive dialog. Even better, you may save the current configuration and make it part of the script. Enough words, let's look at an example session: [root@gate joe]# /tmp/ifc Enter interface configuration data: Interface to configure: [eth0] IP address: [192.168.1.2] 149.71.202.201 Netmask: [255.255.255.0] Gateway ('none' for no gateway): [192.168.1.1] 149.202.71.254 Nameservers (blank separated list): [192.168.1.1] 149.202.71.109 Configuration name (empty if you don't want to save): sni First of all, this will issue the commands /sbin/ifconfig eth0 149.71.202.201 netmask 255.255.255.0 \ broadcast 149.71.202.255 /sbin/route add -net 149.71.202.0 netmask 255.255.255.0 /sbin/route add default gw 149.71.202.254 and create a file /etc/resolv.conf. But additionally the file will modify itself to contain a configuration called I. If you invoke the script with ifc sni later, then the same configuration will be invoked again. =head1 CPAN This script is available as a CPAN script. You can download it from any CPAN mirror, in particular ftp://ftp.funet.fi/pub/languages/perl/CPAN/authors/id/JWIED The following sections are important for CPAN's automatisms only, you can safely ignore them. =head2 SCRIPT_CATEGORIES =head2 PREREQUISITES This script requires the C module, which is part of the core Perl installation since version 5.005. You need to install it manually for previous versions. Additionally required are the C and C modules, which are availably with any Perl version I know, at least 5.002 and later. =head2 OSNAMES This script was developed on a C machine. I see no real problems with porting it to other machines, but you need to modify at least the sections parsing the ifconfig output and the ifconfig and route commands. =head1 COPYRIGHT AND AUTHOR This program is Copyright (C) 1998 Jochen Wiedmann Am Eisteich 9 72555 Metzingen Germany Email: joe@ispsoft.de All rights reserved. You may distribute this script under the terms of either the GNU General Public License or the Artistic License, as specified in the Perl README file. =head1 SEE ALSO L, L =cut ############################################################################ # # Global Variables # ############################################################################ use vars qw($debug $verbose $configurations); my $OLD_PATH = $ENV{'PATH'}; $ENV{'PATH'} = '/sbin:/usr/sbin:/bin:/usr/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; ############################################################################ # # Name: Usage # # Purpose: Print usage message and exit # ############################################################################ sub Usage () { print <<"EOF"; Usage: $0 [options] [configuration] Possible options are: --debug Turn on debugging mode, implies verbose mode. In debugging mode, no changes happen, the program simply prints what would be done. --help Print this message. --verbose By default operation is silent, unless you turn on verbose mode. --version Print version string and exit. EOF exit 0; } ############################################################################ # # Name: Ip2Integer # # Purpose: Convert an IP address into an integer value # # Inputs: $ip - IP address # # Returns: Integer value, dies in case of problems # ############################################################################ sub Ip2Integer ($) { my $ip = shift; die "Invalid IP address: $ip" unless defined($ip) and $ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/; ($1 << 24) | ($2 << 16) | ($3 << 8) | $4; } ############################################################################ # # Name: Integer2Ip # # Purpose: Convert an integer value into an dotted quad # # Inputs: $integer - Integer value # # Returns: Dotted quad string, dies in case of problems # ############################################################################ sub Integer2Ip ($) { my $integer = shift; my $four = $integer & 0xff; $integer >>= 8; my $three = $integer & 0xff; $integer >>= 8; my $two = $integer & 0xff; $integer >>= 8; my $one = $integer; "$one.$two.$three.$four"; } ############################################################################ # # Name: FindIp # # Purpose: Convert a string into an IP address # # Input: $reply - String to convert # # Returns: IP address as dotted quad or undef for an invalid IP address # ############################################################################ sub FindIp ($) { my $reply = shift; if ($reply =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { return undef unless $1 < 256 and $2 < 256 and $3 < 256 and $4 < 256; return "$1.$2.$3.$4"; } # Rats! Need a DNS lookup ... print "Querying IP address of host name $reply ... "; my $ip = Socket::inet_aton($reply); if (!defined($ip)) { print "Cannot resolv.\n"; return undef; } $ip = Socket::inet_aton($ip); # Return an untainted string $ip = $1 if $ip =~ /(.*)/; $ip; } ############################################################################ # # Name: MakeConfig # # Purpose: Enter configuration data and save the configuration in # in interactive dialog. # # Input: $o - Options hash ref # # Returns: Configuration; aborts in case of errors. # ############################################################################ sub MakeConfig ($) { my $o = shift; my $config = {}; my $reply; my $command = "$IFCONFIG -a"; print "Querying interface configuration: $command\n" if $verbose; my $ifconfig = `$command`; print "\nEnter interface configuration data:\n\n"; undef $reply; while (!$config->{'interface'}) { if (defined($o->{'interface'})) { $reply = delete $o->{'interface'}; } else { $reply = $INTERFACE unless defined($reply); print "Interface to configure: [$reply] "; my $r = ; chomp $r; $reply = $r if length($r); } $reply =~ s/^\s+//; $reply =~ s/\s+$//; if ($ifconfig =~ /^($reply)\s+Link encap:/) { $config->{'interface'} = $1; } else { print "An interface $reply doesn't exist.\n"; } } undef $reply; while (!$config->{'ip'}) { if (defined($o->{'ip'})) { $reply = delete $o->{'ip'}; } else { $reply = $IP unless defined($reply); print "IP address: [$reply] "; my $r = ; chomp $r; $reply = $r if length($r); } $config->{'ip'} = FindIp($reply) or print "Invalid IP address: $reply\n"; } undef $reply; while (!$config->{'netmask'}) { if (defined($o->{'netmask'})) { $reply = delete $o->{'netmask'}; } else { $reply = $NETMASK unless defined($reply); print "Netmask: [$reply] "; my $r = ; chomp $r; $reply = $r if length($r); } if ($reply =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) { if ($1 > 255 || $2 > 255 || $3 > 255 || $4 > 255) { print "Invalid Netmask: $reply\n"; } else { my $num = ($1 >> 24) + ($2 >> 16) + ($3 >> 8) + $4; my $netmask = "$1.$2.$3.$4"; my $one = 0; while ($num) { my $bit = $num & 1; $num >>= 1; if ($bit) { $one = 1; } elsif ($one) { print "Invalid Netmask: $reply\n"; undef $netmask; last; } } $config->{'netmask'} = $netmask if defined($netmask); } } elsif ($reply =~ /(\d+)/) { my $bits = $1; if ($bits > 32) { print "Invalid Netmask: $reply\n"; } else { my $num = 0; for (my $i = 0; $i < 32; $i++) { $num = ($num << 1) | ($bits ? 1 : 0); --$bits if $bits; } $config->{'netmask'} = Integer2Ip($num); } } } undef $reply; while (!$config->{'gateway'}) { if (defined($o->{'gateway'})) { $reply = delete $o->{'gateway'}; } else { $reply = $GATEWAY unless defined($reply); print "Gateway ('none' for no gateway): [$reply] "; my $r = ; chomp $r; $reply = $r if length($r); } if ($reply eq 'none') { $config->{'gateway'} = $reply; } else { my $gw = $config->{'gateway'} = FindIp($reply) or print "Invalid IP address: $reply\n"; if (defined($gw)) { my $ip_val = Ip2Integer($config->{'ip'}); my $gateway_val = Ip2Integer($gw); my $netmask_val = Ip2Integer($config->{'netmask'}); if (($ip_val & $netmask_val) != ($gateway_val & $netmask_val)) { print "Gateway $gw doesn't match network.\n"; undef $config->{'gateway'}; } } } } undef $reply; while (!defined($config->{'nameserver'})) { if (defined($o->{'nameserver'})) { $reply = delete $o->{'nameserver'}; } else { $reply = $NAMESERVER unless defined($reply); print "Nameservers (blank separated list): [$reply] "; my $r = ; chomp $r; $reply = $r if length($r); } my $invalid; my @nameservers = map { my $ip = FindIp($_); if (!defined($ip)) { $invalid = 1; print "Invalid IP address: $_\n"; } $ip; } split(/ /, $reply); $config->{'nameserver'} = join(" ", @nameservers) unless $invalid; } undef $reply; while (!defined($config->{'name'})) { if (defined($o->{'name'})) { $reply = delete $o->{'name'}; } else { print "Configuration name (empty if you don't want to save): "; $reply = ; chomp $reply; } $reply =~ s/^\s+//; $reply =~ s/\s+$//; $config->{'name'} = $reply; if (length($reply) and exists($configurations->{$reply})) { print "A configuration $reply already exists.\n"; undef $config->{'name'}; } elsif (length($reply)) { $configurations->{$reply} = $config; # Save this configuration my $dump = Data::Dumper->new([$configurations], ['configurations']); $dump->Indent(1); my $cstr = $dump->Dump($dump); my $file; print "Saving data in file $0\n" if $verbose; if ($0 =~ /\//) { # Absolute path name $file = $0 if $0; } else { foreach my $dir (split(/:/, $OLD_PATH)) { if (-f "$dir/$0") { $file = "$dir/$0"; } } } if (defined($file)) { open(FILE, $debug ? "<$file" : "+<$file") or die "Failed to open $file: $!"; local $/ = undef; my $contents = ; die "Failed to read $file: $!" unless defined($contents); $contents =~ s/(\n__END__\s*\n)(.*)/$1$cstr/s or die "Cannot parse $file"; # Untaint the contents $contents = $1 if $contents =~ /(.*)/s; if ($debug) { print "Writing $file:\n$contents\n"; } else { seek(FILE, 0, 0) or die "Failed to seek in $file: $!"; (print FILE $contents) or die "Failed to write $file: $!"; truncate(FILE, length($contents)) or die "Failed to truncate $file: $!"; } close(FILE) or die "Failed to close $file: $!"; } else { print "Cannot save data: No such file: $0\n"; } } } $config; } ############################################################################ # # Name: UseConfig # # Purpose: Read an existing configuration # # Input: $o - Options hash ref # $name - Configuration name # # Returns: Configuratio hash ref; aborts in case of problems # ############################################################################ sub UseConfig { my $o = shift; my $name = shift; unless (exists($configurations->{$name})) { print "No such configuration: $name\n\n"; print "Available configurations are:\n"; foreach my $c (keys %$configurations) { print " $c\n"; } exit 1; } $configurations->{$name}; } ############################################################################ # # Name: DoConfig # # Purpose: Perform the real configuration # # Inputs: $o - Options hash ref # $config - Configuration hash ref # # Returns: Nothing, aborts in case of trouble # ############################################################################ sub DoConfig { my($o, $config) = @_; my $interface = $config->{'interface'}; my $ip = $config->{'ip'}; my $netmask = $config->{'netmask'}; my $ip_val = Ip2Integer($ip); my $netmask_val = Ip2Integer($netmask); my $bcast = Integer2Ip($ip_val | ~$netmask_val); my $command = "$IFCONFIG $interface $ip netmask $netmask broadcast $bcast"; print "Configuring interface: $command\n" if $verbose; system $command unless $debug; my $network = Integer2Ip($ip_val & $netmask_val); $command = "$ROUTE add -net $network netmask $netmask $interface"; print "Setting interface route: $command\n" if $verbose; system $command unless $debug; my $gateway = $config->{'gateway'}; if ($gateway ne 'none') { $command = "$ROUTE add default gw $gateway"; print "Setting default route: $command\n" if $verbose; system $command unless $debug; } my $nameserver = $config->{'nameserver'}; if ($nameserver) { my $r = "/etc/resolv.conf"; open(FILE, $debug ? "<$r" : "+<$r") or die "Failed to open $r: $!"; local $/ = undef; my $contents = ; die "Failed to read $r: $!" unless defined($contents); $contents =~ s/^\s*nameserver\s+.*?\n//gm; $contents .= join("", map {"nameserver $_\n"} split(/ /, $nameserver)); # Untaint the contents $contents = $1 if $contents =~ /(.*)/s; print "Writing $r:\n$contents\n" if $verbose; unless ($debug) { seek(FILE, 0, 0) or die "Failed to seek $r: $!"; (print FILE $contents) or die "Failed to write $r: $!"; truncate(FILE, length($contents)) or die "Failed to truncate $r: $!"; } close(FILE) or die "Failed to close $r: $!"; } } ############################################################################ # # This is main() # ############################################################################ { if ($>) { print STDERR "Warning: The ifc script is not running as root.\n"; print STDERR "Interface configuration or saving may fail!\n\n"; } # Read the list of configurations { local $/ = undef; my $data = ; $configurations = eval $data; die $@ if $@; } my %o = ( 'debug' => \$debug, 'verbose' => \$verbose ); Getopt::Long::GetOptions(\%o, 'debug', 'verbose', 'version', 'help'); if ($o{'version'}) { print STDERR "$VERSION\n"; exit 1; } $verbose = 1 if $debug and !$verbose; my $cfname = shift @ARGV; Usage() if @ARGV || $o{'help'}; my $config = defined($cfname) ? UseConfig(\%o, $cfname) : MakeConfig(\%o); DoConfig(\%o, $config); } __END__