Archive

Archive for the ‘Code’ Category

TestLab Script in AppleScript

August 14th, 2011 No comments

I got a new Mac Pro workstation at work and re-wrote some scripts to work on it. This morning I couldn’t find the script under the new file lay out, it was in /Applications so I decided I had better document the script so I don’t have to rewrite it if I can’t find it.

It uses the same script, tle, that I wrote a while ago, it just fires up iTerm instead of Gnome Terminal.

-- 2011-03-24
-- Jud Bishop


tell application "iTerm"
    activate
   
    -- If you don't have this you end up with two terminals
    terminate the first session of the first terminal
   
    set iterm to (make new terminal)
   
    repeat with X from 1 to 6
        set Y to "R" & X as string
        tell iterm
            make new session at the end of sessions
            tell the last session
                exec command "/usr/local/bin/tle " & Y & " testlab.chainringcircus.org"
                set name to Y
            end tell
        end tell
    end repeat
   
   
    repeat with X from 1 to 4
        set Y to "SW" & X as string
        tell iterm
            make new session at the end of sessions
            tell the last session
                exec command "/usr/local/bin/tle " & Y & " testlab.chainringcircus.org"
                set name to Y
            end tell
        end tell
    end repeat
   
    repeat with X from 1 to 3
        set Y to "BB" & X as string
        tell iterm
            make new session at the end of sessions
            tell the last session
                exec command "/usr/local/bin/tle " & Y & " testlab.chainringcircus.org"
                set name to Y
            end tell
        end tell
    end repeat
   
    set the bounds of the first window to {0, 0, 1200, 900}
   
end tell
Categories: CCIE, CCIE Labs, Code, Routing Tags:

Data Loss Prevention

March 7th, 2011 No comments

Every once in a while I get to write a neat piece of code that I can share. This is one of those times. I realize it is not large and by PerlMonk standards not very elegant. The problem therein lies with maintainability over the next few years. Regardless I like what I wrote and would like to share.

At the Circus we had a pretty good idea that we had some data leakage. Nothing like people taking off with everything needed to get home loans and rip off customers, just people not thinking about what they send through email. We didn’t know the extent of the problem or even if we had one. We just weren’t sure. Our C-level executives didn’t believe that employees would be so careless with customer data. We decided to find out.

I must say that the results were actually quite positive. We had a couple of people email work related data home so they could work at home over the weekend and a few emails regarding employment, but they were originated by the prospective employee.

Regardless, in order for us to find out I wrote a few scripts that hook into our email system. One that I am particularly proud of recurses through a directory of email messages and attachments scanning each file for relevant data.

Please note that by the time these scripts touch the data it has been scrubbed by the antivirus and other checks we have in place. I am only looking for keywords or regular expressions that would indicate customer related data loss.

Let me explain the directory structure. Under the email system is the directory /var/spool/filter that contains every email message that has been sent in the last 30 minutes. There is a cleanup process that erases all the files in that directory and that is actually where I wrote the hook, in the cleanup process. Here is a sample listing of the directory.

#ls -1 /var/spool/filter/
msg-1299451572-29517-0
msg-1299451626-29523-0
msg-1299451695-29528-0
msg-1299452467-29565-0
msg-1299452491-29570-0
msg-1299453007-29593-0
msg-1299453086-29599-0

As you can see, each email header ends with a .hed extension and the message is in .txt format. The ETP.doc file is an attachment.

#ls -1 /var/spool/filter/msg-1299451626-29523-0/
ETP.doc
msg-29523-1.txt
msg-29523-2.dat.hed

The subroutine I am most pleased with is the one that recurses through the directory structure. The slurp command returns a hash and if it is a subdirectory then it is a hash as well. I look for it with the following line of code.

if (ref $structure->{$key} eq 'HASH')

That is how I find subdirectories to push onto the stack of recursive calls. As it traverses each directory it just looks at each file extension and makes a determination as to what to do with it.

I realize most system administrators are asking why I didn’t use the file command to make sure the script was acting appropriately for each file type but that does not work with the new Microsoft document types.

# file Test-Excel.xlsx
Test-Excel.xlsx: Zip archive data, at least v2.0 to extract

I thought it was a fun project and I enjoyed writing what I felt was an interesting piece of code.

#!/usr/bin/perl
# 2011-01-12 Jud Bishop
# This script goes looking for customer data being sent out through email and
# flags it for further review.
use strict;
use warnings;
use File::Find;
use File::Basename;
use File::Copy::Recursive qw(fcopy dircopy rcopy);
use File::Slurp::Tree;

#my $dir = "/home/jud/TestMessages";
#my $log = "/home/jud/TestMessages/violation";
#my $auditdir = "/home/jud/TestMessages/Trash/";
my $dir = "/var/spool/filter";
my $log = "/var/log/hipaa/violation";
my $auditdir = "/opt/smtpaudit/";
my $debug = 0;


###################
# MAIN
###################
my %tree;
my $tree = slurp_tree($dir);

open (LOG, '>>', $log) or die $!;

traverse_structure($dir, $tree);

close LOG or die $!;


##########
# This does the heavy lifting of the whole program.  It recursively
# iterates through the directory structure and works on a file accordingly.
# Each directory is a hash key.
##########
sub traverse_structure {
        if($debug){print "##traverse_structure\n";}
        my ($base, $structure) = @_;
        my $path;
    my @violation;
    my $secure;
        foreach my $key ( keys %$structure) {
                $path = $base . "/" . $key;
        $secure = 0;
        ## If it's a HASH then it's a directory.
                if (ref $structure->{$key} eq 'HASH'){
            if($debug){print "key: $key\n"};
                        traverse_structure( $path, $structure->{$key} );
                } else {
            if($debug){print "file  : $key\n"};
            if($debug){print "base  : $base\n"};
            if($debug){print "path  : $path\n"};
            if($debug){print "secure: $secure\n"};
            if($debug){print "violation: $#violation\n"};
   
            ## If the file is not being used...
            if ($path =~ m/doc$/){
                parse_doc($path, \@violation);
            } elsif ($path =~ m/xlsx$|xls$/) {
                parse_excel($path, \@violation);
            } elsif ($path =~ m/txt$/) {
                parse_message($path, \@violation);
            } elsif ($path =~ m/pdf$/) {
                parse_pdf($path, \@violation);
            } elsif ($path =~ m/hed$/) {
                parse_head($path, \@violation, \$secure);
            }
                }
    }
       # If it is a secure email than it is encrypted on
       # the fly and not a violation.
    if ( ($secure == 0) && ($#violation > 3) ){
        push (@violation, "EMAIL: " . $base);
        log_it(@violation);
        copy_dir($base);
    }
}

# For later review.
sub copy_dir {
    my $path = shift;
    if($debug){print "##copy_dir $path\n";}
    my $file = fileparse($path);
   
    if ($file =~ m/^msg/){
        my $basename = basename($path);
        my $newpath = $auditdir . $basename;
   
        if($debug){print "dircopy $path $newpath\n";}
        dircopy($path,$newpath);
    }
}

# Log file that is easy to ready because an employee goes through
# this file and decides if it is a REAL violation.
sub log_it {
    my @text = @_;
    my $line;
    if($debug){print "##log_it\n";}
    print LOG "---------------------------------------------\n";
    foreach $line (@text) {
        print LOG "$line\n";
    }
    print LOG "---------------------------------------------\n";
}

sub parse_head {
    my ($file, $violation_ref, $secure_ref) = @_;
    my @body;
    my $line;
    if($debug){print "##parse_head $file\n";}

    open(FILE,$file) || return 0;
        @body = <FILE>;
    close(FILE);

    foreach $line (@body)   {
        if ($line =~ m/^From/){
                        push (@$violation_ref, $line);
        } elsif ($line =~ m/^To/) {
                        push (@$violation_ref, $line);
        } elsif ($line =~ m/^Subject/) {
                        push (@$violation_ref, $line);
            if ($line =~ m/^secure/i )
            {
                $$secure_ref = 1;
            }
        }
    }
}

sub parse_pdf {
    my ($file, $violation_ref) = @_;
    my @body;
    my $new_file = $file . ".txt";
    my $CMD;

    if($debug){print "##parse_doc $dir $file\n";}
    $CMD = "/usr/bin/pdftotext \"" . $file . "\" > \"" . $new_file . "\"";
    if($debug){print "CMD: $CMD\n";}
        system($CMD);
        parse_text ($new_file, $violation_ref);
}

sub parse_doc {
    my ($file, $violation_ref) = @_;
    my @body;
    my $new_file = $file . ".txt";
    my $CMD;

    if($debug){print "##parse_doc $dir $file\n";}
    $CMD = "/usr/bin/antiword -st \"" . $file . "\" > \"" . $new_file . "\"";
    if($debug){print "CMD: $CMD\n";}
        system($CMD);
        parse_text ($new_file, $violation_ref);
}

sub parse_excel {
    my ($file, $violation_ref) = @_;
    my @body;
    my $new_file = $file . ".txt";
    my $CMD;

    if($debug){print "##parse_excel $file\n";}
    $CMD = "/usr/local/bin/antiexcel \"" . $file . "\" > \"" . $new_file . "\"";
    if($debug){print "CMD: $CMD\n";}
        system($CMD);
        parse_text ($new_file, $violation_ref);
}

sub parse_text {
    my ($file, $violation_ref) = @_;
    my @body;
    if($debug){print "##parse_text $file\n";}

    open(FILE,$file) || return 0;
        @body = <FILE>;
    close(FILE);

    compare_text(\@body, $violation_ref);
}

sub parse_message {
    my ($file, $violation_ref) = @_;
    my @body;
    if($debug){print "##parse_text $file\n";}

    open(FILE,$file) || return 0;
        @body = <FILE>;
    close(FILE);

    compare_text(\@body, $violation_ref);
}

# All of the earlier subroutines call this one.  
# It takes the text and looks for keywords.
sub compare_text {
    my ($text_ref, $violation_ref) = @_;
        my @difference;
    my @text_array;
    my @elements;
        my %count;
        my %rules;
        my $element;
    if($debug){print "##compare_text\n";}

    foreach $element (@$text_ref){
            @elements = split(' ', $element);
        push (@text_array, @elements);
    }

        # The parser was already created above.
        my @rule = ("DOB", "D.O.B.", "d.o.b.", "dob", "death:", "release", "admit", "admission", "Age:", "SSN", "Social", "Security", "Account", "Acct", "claimant", "MRI", "myelogram", "credit", "card");

    # Me being lazy.
        foreach $element (@rule)
        {
                $rules{$element} = 1;
        }

        foreach $element (@text_array)
        {
                if (exists $rules{$element})
                {
            if($debug){print "$element\n";}
            $element = "VIOLATION: " . $element;
                        push (@$violation_ref, $element);
                }
                # Social Security Number
                elsif($element =~ /\d{3}-?\d{2}-?\d{4}/)
                {
            if($debug){print "$element\n";}
            $element = "VIOLATION: " . $element;
                        push (@$violation_ref, $element);
                }
                # Credit Card Number or MRN
                elsif($element =~ /\d{4}-?\d{4}-?\d{4}-?\d{4}/)
                {
            if($debug){print "$element\n";}
            $element = "VIOLATION: " . $element;
                        push (@$violation_ref, $element);
                }

        }
}
Categories: Code, Linux Tags:

Password Aging

January 28th, 2011 No comments

At the Circus we have a password policy to change all passwords every 90 days. Today it was brought to my attention that one of the linux servers was not following that policy. I confirmed that was true and after a little digging I found that it was only accounts that had been migrated from AIX to linux. But we couldn’t force around 2000 users to all change their passwords at the same time because we would inundate the help desk.

This is the script that I wrote to fix the problem and distribute the password changes over a month. The result is that there are only 78 users per day that are forced to change their password each day over a 28 day period.

#!/bin/bash
# 2011-01-28
# Jud Bishop
# Checks for passwords set to never expire and gives an expiration date.
# Distributes the password changes over a 28 day spread.

X=0;

for I in `cat /etc/passwd | cut -d: -f 1`
do
        #echo $I
        #chage -l $I | egrep "Password expires" | cut -d : -f 2

        DATE=`chage -l $I | egrep "Password expires" | cut -d : -f 2 | cut -d \  -f 2`
        if [ $DATE = "never" ]
        then
                echo $I
                if [ $X -le "27" ]
                then
                        X=`expr $X + 1`
                else
                        X=1;
                fi
                echo $X $I
                chage -d  2010-11-$X -M 90 $I
        fi
done
Categories: Code, Linux Tags:

Another TestLab Script

January 5th, 2011 No comments

I’m sorry that all of these TestLab scripts are a recurring theme. Work purchased four 3560s and two 1841s for the lab so I have been updating all of my scripts. When I was working on the lab I kept having sessions hang so I wrote a quick script to clear all of the lines on the terminal server.

#!/usr/bin/expect
# 2010-12-14 Jud Bishop
# tl-clear
# A short script to handle logging into a router in the lab.

set host "testlab.chainringcircus.org"
set pass "CHANGEME"
set enable "CHANGEME2"
set ctrlz \032

##############################
# Should not need any more changes.

spawn telnet $host
expect "Password:"
send "$pass\r"
expect "testlab>"
send "enable\r"
expect "Password:"
send "$enable\r"
expect "testlab#"
sleep 1

for { set i 1} {$i < 48} {incr i 1} {
    send "clear line $i\r"
    expect {
        -re ".*confirm.*" {send "y \r"}
        -re ".*Not allowed to clear current line.*" {send "\r"}
        -re ".*Invalid input detected at.*" {send "\r"}
    }
}
exit
Categories: Code, Routing Tags:

Red Hat Upgrades

December 3rd, 2010 No comments

Now that RHEL6 is out I’ve begun playing with upgrading our older RHEL servers, starting with anything that is RHEL4 and then moving forward to RHEL5. The simplest one to start with was one of our network management servers. They are vital to our job but not customer facing and we can deal with some downtime on them.

My original plan was to install a base RHEL4 on the new server, which would give us a platform with the base system installed and the restore software hosted, then restore from backup over the top.

I took an old server we had that still had maintenance and pressed it into service. I wanted to take the opportunity to test our backups as well as the upgrade path from RHEL4 to RHEL6. So I installed RHEL4 on the new server and ran up2date to make sure it was the “latest” and greatest.

On the old server I ran up2date and then I queried the rpm database to see what packages were installed. The problem is that you cannot pipe rpm -qa output as input into an update script. Up2date wants “freetype-devel” as the package name, not the whole package name as listed in the output, “freetype-devel-2.1.9-17.el4_8.1″ note the version number. Notice also that some package names have multiple dashes while others have only one dash.

[root@server] # rpm -qa
freetype-devel-2.1.9-17.el4_8.1
krb5-devel-1.3.4-62.el4_8.3
php-4.3.9-3.31

I could not easily use cut in a simple bash script so I wrote the following quick hack.

#!/usr/bin/perl

# 2010-12-03 Jud Bishop
# Run:
# rpm -qa >/tmp/installed-software.log
# Then run:
# for I in `rhel-upgrade.pl`; do up2date $I; done

use strict;

my $log_file = "/tmp/installed-software.log";

open (FILE, $log_file) or die "Error: can't open log file\n $! \n";
while (<FILE>)
{
    chomp;
    my (@log) = split /-/;
    my $package = "";

    for (my $i = 0; $i <= $#log; $i++)
    {
        if ( $log[$i]  =~ /^[a-zA-Z]/ )
        {
            if ( $i > 0 )
            {
                $package = $package . "-" . $log[$i];
            } else {
                $package = $log[0];
            }
        }
    }
    print "$package \n";
}

close FILE or die "Error: can't close file\n $! \n";

It has been interesting testing our backup software. It appears it will take some refinement for us to get the restore process worked out.

Categories: Code, Linux Tags:

TSHOOT Tickets

November 15th, 2010 3 comments

I took and did not pass my TSHOOT exam last week. Bummer. I had not studied specifically for the test very hard. In my mind I had already started thinking about the CCIE written and even started doing the “easy” CCIE labs. I have a long way to go as the best I could do was a 2 hour CCIE lab in 5 hours. That was the best. There were a few that took a couple of days to complete. So when I went to take my test I felt I had a good chance at passing but was by no means assured of a passing grade.

Without breaking the NDA here are some of my thoughts.

  • I was not prepared mentally.
  • I did not have a game plan for troubleshooting the lab.
  • I did not go over the basic troubleshooting commands before I took the test.
  • I was more prepared for this specific test in May than I was last week.
  • How I prepared for the last test:
    I went and reviewed this blog. Reading my own writing and doing the commands on the lab routers refreshed some of the old memories.

    I configured the TSHOOT topology from scratch again. Then I had my coworker go through and randomly break things. He would log in and break it, give me a hint and then I would go in and fix the configuration. This was easy, possibly too easy, and it was slow. We would only do this once a day and I wouldn’t worry if it took 15 or 20 minutes, I was just playing around in the lab before I went home. That all changed during the test. Because I had configured my lab I knew the configs cold, I had configured it all and I could spot typos or simple errors quickly by just viewing the running configuration. That is not the case during the test.

    How I am preparing now:
    I am not concerned about theory. I have been studying TCP/IP Volume I with the CCIE written in mind and am on the last chapter.

    Over the weekend I made 15 different configurations with errors. I don’t even remember what each ticket does, just that it creates an error somewhere in the lab. I saved them to the flash of each switch or router and wrote the following script. It randomly selects a trouble ticket to load, then calls the testlab update expect script, tlue, and loads the bad configuration on the device.

    At this point I am not concerned with the actual error. I did not have a process to follow last week during the test and was not consistent in my troubleshooting process. By being able to load a number of errors in quick succession I will be able to troubleshoot a number of errors using a consistent process. My goal is to bombard myself with random errors like I saw in the test and get my troubleshooting process down pat.

    The other key here is to rely on mastering a few commands that tell me the most about the situation on a router or switch and not on reading router configurations to troubleshoot. I feel that was my weakness in my previous studies and became my weakness during the test.

    Below is the script that loads random configurations with errors in the lab.

    #!/bin/bash
    # 2010-11-15 Jud Bishop
    # tl-ticket
    # This script randomly picks a ticket and loads the configuration from flash.

    I=$((RANDOM%15+1))

    #1: DSW1
    #2: ASW1
    #3: ASW1
    #4: DSW2
    #5: R3
    #6: R4
    #7: R2
    #8: R1
    #9: R1
    #10: R2
    #11: R2 -- IPv6
    #12: DSW1
    #13: R4
    #14: R4 -- IPv6
    #15: R4

    case $I in
        1 )
            tlue DSW1 replace ticket1.cfg
        ;;
        2 )
            tlue ASW1 replace ticket2.cfg
        ;;
        3 )
            tlue ASW1 replace ticket3.cfg
        ;;
        4 )
            tlue DSW2 replace ticket4.cfg
        ;;
        5 )
            tlue R3 replace ticket5.cfg
        ;;
        6 )
            tlue R4 replace ticket6.cfg
        ;;
        7 )
            tlue R2 replace ticket7.cfg
        ;;
        8 )
            tlue R1 replace ticket8.cfg
        ;;
        9 )
            tlue R1 replace ticket9.cfg
        ;;
        10 )
            tlue R2 replace ticket10.cfg
        ;;
        11 )
            tlue R2 replace ticket11.cfg
        ;;
        12 )
            tlue DSW1 replace ticket12.cfg
        ;;
        13 )
            tlue R4 replace ticket13.cfg
        ;;
        14 )
            tlue R4 replace ticket14.cfg
        ;;
        15 )
            tlue R4 replace ticket15.cfg
        ;;
    esac
    Categories: CCNP TSHOOT, Code Tags:

    Even More TestLab Scripts

    October 6th, 2010 No comments

    I doubt many home labs match hardware exactly with whatever the vendor has recommended. As a result most of us must script around or manually edit vendor configuration files. Here are some of my earlier scripts that I have shared that have been updated and a couple of new ones.

    tl-checklist — TestLab CheckList
    So that others will be able to understand the process, I have decided to walk you through the use of these scripts. The first one started out as a checklist of changes to make to each different device for every lab, but that was quickly replaced with a script. Funny thing about scripts like this, it should have probably been written in Perl but I did not expect it to grow to this size.

    Each lab has its own directory of configuration files and this script works its way through each configuration file making changes as it goes. Here is an output of the usage example.

    Usage: tl-checklist LAB-X
       tl-checklist LAB-1

    I have tried to make the script idiot proof for my own sake. The first thing it does is make a copy of the directory so that a backup of the original exists. Then it puts a marker in the directory so that the script knows whether it has updated those configuration files before. If you have run the script on that directory it will fail gracefully.

    if [[ $# -ne 1 ]]
    then
            echo -e "Usage: ${0} LAB-X\n ${0} LAB-1\n"
            exit 1
    elif [[ -e "/tftpboot/VOL-1/${LAB}/check-list" ]]
    then
        echo -e "\n${0} has been run on this directory before."
        echo -e "Please remove /tftpboot/VOL-1/${LAB}/check-list."
        echo -e "And run the script again."
        exit 1
    else
        touch "/tftpboot/VOL-1/${LAB}/check-list"

        if [[ ! (-d /tftpboot/VOL-1/${LAB}.0) ]]
        then
            cp -r "/tftpboot/VOL-1/${LAB}" "/tftpboot/VOL-1/${LAB}.0"
        fi
    fi

    Finally it loops through each device and makes changes that are need for each device to work properly with the other scripts and for the configuration to actually match my hardware connections.

    for I in R1 R2 R4 R5 R6 R7 R9 Cat1 Cat2 Cat3 Cat4 BB1 BB2 BB3
    do
        CONF="/tftpboot/VOL-1/${LAB}/INITIAL/${I}.txt"
        # The $$ on the end is just in case you run this script multiple times
        # the oldest file in an ls -tlr will be the true original.
        SAVE="/tftpboot/VOL-1/${LAB}/INITIAL/${I}.txt.$$"
       
        if [ -e $CONF ]
        then
            mv $CONF $SAVE

            echo $I
       
            case $I in
            R1 )
                sed 's/^boot.*//g
                s/^warm.*//g
                s/clock rate 2000000/clock rate 128000/g' $SAVE >$CONF
            ;;
    The rest is removed for brevity.

    The long term reader will recognize that this script dovetails nicely with the previous testlab scripts. For instance, the backbone routers and catalyst switches are renamed to match the hostname expected in the upgrade scripts. It also uses the same command line switches so that it integrates with some of the earlier scripts.

    tl-update-router — TestLab Update Router
    The next script draws on tlue (testlab update expect) to go through and upgrade any number of switches or routers.

    Usage: tl-update-router -l LAB-X -r Rx [dtn]
     -r Rx is required.
         If -r equals all, then the entire lab will have the specified configuration loaded.
         If -r equals routers, then the routers in the lab will have the specified configuration loaded.
         If -r equals switches, the switches in the lab will have the specified configuration loaded.
     -d load flash:def first.
     -t load flash:new from tftp
     -l LAB-X is required if tftp or new is specified.
     -n run configure replace flash:new
     -c configure the router from the console
    tl-update-router -l LAB-1 -r all -dc

    The final line of the usage example is how I normally run this script. This particular example tells the script to upgrade all of the switches and routers to the initial configuration for LAB-1.

    tl-update-router -l LAB-1 -r all -dc

    In doing so it will first run a configure replace command loading the flash:def file, then it will load the LAB-1 configuration file from the console. The reason I moved from configure replace of a tftp copied file onto flash was because I was not getting consistent results. If, however, I went back and pasted the switch or router configuration over the console input the configuration would take and as a result I just switched to having the script input the configuration over the console session.

    tlue — TestLab Update Expect
    The tlue script handles the interaction of the switches and routers through expect. A friend gave me a more thorough example but it seemed overkill for my purposes. By this time I know where I usually have problems, the serial interface of R2, but other than that this script works well.

    This procedure in the tlue script is the one that sends the configuration over the console cable.

    proc send_config {lab router} {

        puts "send_config"
        puts $lab
        puts $router
        global spawn_telnet
        set spawn_id $spawn_telnet
        expect "$router\#" {send "conf t\r\r"}
        expect -re ".*Enter configuration.*" {send "\r"}

            set file [open /tftpboot/VOL-1/$lab/INITIAL/$router.txt r]
            foreach line [ split [read $file] "\n"] {
            expect -re "$router.*" {send "$line \r"}
            }
            close $file
    }

    If your hardware matches that of your vendor I would recommend running tl-update-route with the configure replace option, otherwise it is porbably best to configure it from the console.

    tl — TestLab
    Finally, this is the last script I run which logs me into each every router or switch. It uses tlr for each terminal instance to log into the correct device, then changes the title accordingly. I do not use a “&” at the end of the command to execute in a subshell because it will not automatically log out of every session when terminating the window. Regardless, control of the terminal will be returned to you.

    Categories: Code, Routing Tags:

    Two Variables, One Line

    August 24th, 2010 No comments

    This morning I could not for the life of me remember how to read two variables from one line in bash. As a result I am putting this simple script up here so that I have an easy place to reference.

    The input file was a listing of printer IP addresses that are translated is in the file /tmp/printers.txt and looks like this.

    cat /tmp/printers.txt
    192.168.1.10  =  10.0.1.10
    192.168.50.5  =  10.0.1.11
    192.168.50.15  =  10.0.1.25

    Here is the simple code to read both variables.

    #!/bin/bash
    # 2010-08-24 Jud Bishop
    # Simple script to find names of local and remote printers
    # that are translated.

    while IFS== read remote local  
    do
            name=`dig +short -x $local`
            echo -e "$name,$remote,$local"
    done < /tmp/printers.txt

    But it came it out in this format, not much of a problem but I prefer it more legible.

    tcp5.chainringcircus.org.,192.168.1.10   ,   10.0.1.10
    rmp7.chainringcircus.org.,192.168.50.5   ,   10.0.1.11
    jlb3.chainringcircus.org.,192.168.50.15   ,   10.0.1.25

    So I cleaned up the output. The first sed stanza deletes the third “.” in the output and the second sed stanza deletes the spaces.

    ./find-printers.sh | sed 's/\.//3
    s/\ //g'
    tcp5.chainringcircus.org,192.168.1.10,10.0.1.10
    rmp7.chainringcircus.org,192.168.50.5,10.0.1.11
    jlb3.chainringcircus.org,192.168.50.15,10.0.1.25
    Categories: Code Tags:

    More TestLab Scripts

    June 25th, 2010 No comments

    I wrote a couple more scripts this week. These two little gems update my entire lab by running one command from the testlab tftp server. I pass the main script a lab name and it proceeds to update every router and switch in the testlab to the new initial lab configuration. I have tested it on at least one 1841, 2611, 3640, 3550 and 3560. I probably spent more time writing the scripts than it would have taken me to upgrade the testlab by hand for each lab, but I guess I’ll never know. You can download the scripts here.

    Let me explain how it is done. Every router and switch has a configuration file named flash:def. When the whole lab has flash:def loaded they can all get to the tftp server for the lab. First side note: it was named flash:default but default is a reserved word in expect so I had to change the name, def is short for default.

    In the first round of interaction the script saves every running-config to flash:new and then does a configure replace flash:def. If you just wanted to reset your lab each time, you could stop the script there. You should make sure that the switches all get changed to a VTP mode transparent and set to a new VTP domain, oddly enough I used def as my VTP domain. By making all of the switches transparent if the new config loads any VLANs or changes the domain your VTP domain will be reset. Second side note: I was going to save the original running-config as flash:save, that would still be a trivial change to make and then you could go back and review the config if you needed, but some labs build on top of the next, so I didn’t.

    After each router and switch has the def configuration file loaded the script does an ls of the correct lab directory on the tftp server and proceeds to tftp the new configuration file as flash:new, overwriting the previously saved running-config. The tftp server is hooked to the switch named Cat4 which is the last host upgraded by the script so all devices can get to the tftp server.

    Finally the script goes back through each device doing a round of configure replace:new. The best part of this is that there is no reload. The 2611 can take nearly 10 minutes to reload so I did not want to have to reload any devices.

    In summary this is the process:
    1. Save every running-config as flash:new.
    2. Load the default configuration flash:def that allows for tftp access for the entire lab.
    3. Copy the initial configuration for the new lab to flash:new on the routers that need it, overwriting the saved flash:new.
    4. Run configure replace flash:new on ever device, bringing up the newest lab.

    Here is a listing of the files on R2.

    R2#dir flash:
    Directory of flash:/

        1  -rw-    32632600                    <no date>  c3640-a3js-mz.124-25b.bin
        2  -rw-        1132                    <no date>  r2-basic-ios.cfg
        3  -rw-        1200                    <no date>  r2-gre.cfg
        4  -rw-        6446  May 28 2002 02:23:57 +00:00  ipbasic.cfg
       13  -rw-         883                    <no date>  save
       15  -rw-        1061                    <no date>  def
       18  -rw-        1226                    <no date>  new

    33030140 bytes total (372576 bytes free)
    R2#

    This is the tlu script. It is just the simple front end to the tlue script.

    #!/bin/bash
    # 2010-05-25 Jud Bishop
    # tlu (testlab update)
    # One of two scripts that updates the entire testlab to the current lab.
    # This script calls tlue (testlab update expect) that does the heavy lifting.

    if [ $# -ne 1 ]
    then
            echo -e "Usage: ${0} LAB-X\n ${0} LAB-1\n"
            exit 1
    fi

    LAB=${1}

    # First go through and set every router or switch to the default configuration.
    # Calling tlue (testlab update expect) with the def command, the def
    # configuration sets every router to clean confiuration that allows access to
    # the tftp server.  
    # R3 is the frame switch so it is not included.
    config_replace ()
    {
            LAB=${1}
            CONFIG=${2}
            for ROUTER in R1 R2 R4 R5 R6 R7 R8 R9 BB1 BB2 BB3 Cat1 Cat2 Cat3 Cat4
            do
                    echo $ROUTER $LAB $CONFIG
                    tlue $ROUTER $LAB $CONFIG
                    sleep 10
            done
    }

    #
    ## Main
    #
    config_replace $LAB "def"

    for CONFIG in `ls /tftpboot/VOL-1/${LAB}/INITIAL/`
    do
            ROUTER=`basename $CONFIG .txt`
            echo $ROUTER
            tlue $ROUTER ${1} tftp
            sleep 10
    done

    config_replace $LAB "new"

    The expect script is the one that does all of the work and took the most time to write.

    #!/usr/bin/expect -f
    # 2010-06-25 Jud Bishop
    # tlue (testlab update expect)
    # This script does the heavy lifting of updating the testlab.
    # If you use this script you will have to change the IP address
    # in the procedure copy_tftp.

    set host "testlab.chainringcircus.org"
    set pass "CHANGEME"
    set ctrlz \032
    set timeout 100
    set spawn_telnet 0

    # Sent in from the command line.
    set router [lindex $argv 0]
    set lab [lindex $argv 1]
    set config [lindex $argv 2]

    proc login {router} {
            puts "login"
            global host pass ctrlz spawn_telnet
            spawn telnet $host
            expect "Password:" {
                    send "$pass\r"
            }

            expect "testlab>" {
                    send "telnet $router\r"
            }
            sleep 2

            expect "Open" {
                    send "\r"
            }
            sleep 2

            # Just in case we are in configure mode.
            send "$ctrlz"
            send "\r"
            set spawn_telnet $spawn_id
    }

    proc save_config {router} {
            puts "save_config"
            global spawn_telnet
            set spawn_id $spawn_telnet
            # Saving the current config in case I want it for some reason.
            expect "$router#" {
            send "copy run flash\:new\r"
            }

            # Destination filename [new]?
            expect {
                     "\[new\]" {send "\r"}
            }

            #Do you want to over write? [confirm]
            expect {
                    -re ".*Do you want to over write.*" {send "y\r"}
                    "$router#" {send "\r"}
            }
            sleep 2

            # No, do not erase flash:.
            # Erase flash: before copying? [confirm]
            expect  {
                     -re "Erase flash\:" {send "n"}
                    "$router#" {send "\r"}
            }
            sleep 2
    }

    # Pass in the file to load from flash, for me it's either "new" or "def".
    proc configure_replace {config router} {
            puts "configure_replace"
            global spawn_telnet
            set spawn_id $spawn_telnet
            # This gets the router/switch to a known configuration that can reach the ftp server.
            expect "$router#" {
                    send "configure replace flash\:$config\r"
            }

            #configure replace flash:default
            #This will apply all necessary additions and deletions
            #to replace the current running configuration with the
            #contents of the specified configuration file, which is
            #assumed to be a complete configuration, not a partial
            #configuration. Enter Y if you are sure you want to proceed. ? [no]:
            expect -re "(.*)(no)(.*)" {
                    send "y\r"
            }
            sleep 5
    }

    proc copy_tftp {lab router} {
            puts "copy_tftp"
            global spawn_telnet
            set spawn_id $spawn_telnet
            #R1#copy tftp://192.168.1.234/R1.txt flash:new
            expect "$router\#" {
                    send "copy tftp://192.168.1.234/VOL-1/$lab/INITIAL/$router.txt flash:new\r"
            }

            #Destination filename [new]?
            expect {
                    -re "Destination filename" {send "\r"}
            }

            #Erase flash: before copying? [confirm]n
            #OR
            #Do you want to over write? [confirm]y
            #expect -re {
            #       "(.*)(before copying)(.*)" {send "n\r"}
            #       "(.*)(over.write)(.*)" {send "y\r"}
            #}
            #Do you want to over write? [confirm]
            expect {
                    -re ".*Do you want to over write.*" {send "y\r"}
                    "$router#" {send "\r"}
            }
            sleep 2

            #If it was over write above, now it might ask for erase flash or
            #return the router prompt.
            #Erase flash: before copying? [confirm]
            expect {
                    -re "Erase flash" {send "n\r"}
                    "$router#" {send "\r"}
            }

            #Loading R1.txt from 192.168.1.234 (via FastEthernet0/0): !
            #[OK - 902 bytes]
            #Verifying checksum...  OK (0xFBC4)
            #902 bytes copied in 0.232 secs (3888 bytes/sec)
            #R1#

            expect "$router#" {
                    send "\r"
            }
    }

    #
    ## Main
    #
    # Oh the irony.  If only I had known how many problems calling my "default"
    # configuration flash:default would cause me.  As a result I changed it to def.
    # However instead of a nice switch statement I'm using an if block, default is a
    # reserved word in Tcl switch statements.  I did not take the time to go back and
    # change it.
    puts "router $router"
    puts "lab $lab"
    puts "config $config"

    if {[ string compare $config tftp ] == 0}  {
            login $router
            copy_tftp $lab $router
    } elseif {[ string compare $config new ] == 0}  {
            login $router
            configure_replace new $router
    } elseif {[ string compare $config def ] == 0} {
            login $router
            save_config $router
            configure_replace def $router
    } else {
            puts "tlue router lab \[def new tftp\]"
            puts "Example:"
            puts "tlue R1 LAB-1 def"
            puts "default saves the running-config to flash:new and runs configure replace:flash:default"
            puts "tftp loads the correct configuration for the lab from the tftp server as flash:new"
            puts "new runs configure replace flash:new, replacing the running config with new"
            exit
    }

    exit
    Categories: Code, Routing Tags:

    TestLab Scripts

    June 18th, 2010 2 comments

    I wrote a couple of scripts for the testlab this week and figured I would share them. You can download them here. These could be modified for a GNS3 lab setup easily as well. Not wanting to reinvent the wheel I googled around and started hacking another script. It started off with something like this:

    #!/bin/sh
    # Usage: $0 [command]
    pgrep -u "$USER" gnome-terminal | grep -qvx "$$"
    if [ "$?" -eq 0 ]; then
      WID=`xdotool search --class "gnome-terminal" | head -1`
      xdotool windowactivate $WID
      #xdotool key ctrl+shift+t
      xdotool key ctrl+t

    But gnome-terminal sets the environmental variable WINDOWID so I began by changing it to:

    #!/bin/sh
    # Usage: $0 [command]

    xdotool windowactivate $WINDOWID
    xdotool key ctrl+t

    But then I read the gnome-terminal manpage to see what other environmental variables it set and decided all the xdotool commands were too much for what I needed. So I simplified into two main scripts, one to handle the gnome-terminal interactions and one to handle the router interactions.

    #!/bin/bash
    # 2010-06-14 Jud Bishop
    # tlr
    # This script opens a single gnome-terminal tab and log into a router in
    # the testlab.

    if [ $# -ne 1 ]
    then
        echo -e "Usage: ${0} router_id\n tlr R1\n"
        exit 1
    fi

    gnome-terminal --tab -e "tle ${1}" -t "${1}"

    The expect script to handle the interaction with the 2511-RJ getting logged into the router.

    #!/usr/bin/expect
    # 2010-06-14 Jud Bishop
    # tle
    # A short script to handle logging into a router in the lab.

    set host "testlab.chainringcircus.org"
    set pass "CHANGEME"

    ##############################
    # Should not need any more changes.
    set router [lindex $argv 0]

    spawn telnet $host
    expect "Password:"
    send "$pass\r"
    expect "testlab>"
    send "telnet $router\r"
    sleep 1
    send "\r"
    sleep 1
    send "\r"
    interact
    exit

    And finally the script that logs into every router in the lab, renaming the tab title to match the router name.

    #!/bin/bash
    # 2010-06-14 Jud Bishop
    # tl
    # This script fires up gnome-terminal with a bunch of tabs each executing
    # the tle script and naming the tab with the router name.

    gnome-terminal --tab -e "tle R1" -t "R1" \
    --tab -e "tle R2" -t "R2" \
    --tab -e "tle R3" -t "R3" \
    --tab -e "tle R4" -t "R4" \
    --tab -e "tle R5" -t "R5" \
    --tab -e "tle R6" -t "R6" \
    --tab -e "tle R7" -t "R7" \
    --tab -e "tle R8" -t "R8" \
    --tab -e "tle R9" -t "R9" \
    --tab -e "tle Cat1" -t "Cat1" \
    --tab -e "tle Cat2" -t "Cat2" \
    --tab -e "tle Cat3" -t "Cat3" \
    --tab -e "tle Cat4" -t "Cat4" \
    --tab -e "tle BB1" -t "BB1" \
    --tab -e "tle BB2" -t "BB2" \
    --tab -e "tle BB3" -t "BB3"
    Categories: Code, Routing Tags: