r3302 deviceconfig: new gateway, clone from 'switchvlans'
authorDenis Ovsienko <infrastation@yandex.ru>
Mon, 1 Mar 2010 12:45:26 +0000 (12:45 +0000)
committerDenis Ovsienko <infrastation@yandex.ru>
Mon, 1 Mar 2010 12:45:26 +0000 (12:45 +0000)
gateways/deviceconfig/cisco.connector [new file with mode: 0755]
gateways/deviceconfig/cisco.secrets.php-sample [new file with mode: 0644]
gateways/deviceconfig/main [new file with mode: 0755]

diff --git a/gateways/deviceconfig/cisco.connector b/gateways/deviceconfig/cisco.connector
new file mode 100755 (executable)
index 0000000..57f8bb7
--- /dev/null
@@ -0,0 +1,207 @@
+#!/bin/sh
+
+[ $# = 7 ] || exit 1
+
+ENDPOINT=$1
+HW=$2
+SW=$3
+COMMAND=$4
+FILE1=$5
+FILE2=$6
+FILE3=$7
+MYDIR=`dirname $0`
+ostype=`uname -s`
+case "$ostype" in
+       Linux)
+       SEDFLAG='-r'
+       ;;
+       FreeBSD)
+       SEDFLAG='-E'
+       ;;
+       *)
+       exit 7
+esac
+
+
+prepare_connect_commands()
+{
+       [ $# = 1 ] || exit 2
+       local skip=yes cval found=no
+       while read line; do
+               if [ "$skip" = "yes" -a "$line" = "# S-T-A-R-T" ]; then
+                       skip=no
+                       continue
+               fi
+               if [ "$skip" = "no" -a "$line" = "# S-T-O-P" ]; then
+                       skip=yes
+                       continue
+               fi
+               [ "$skip" = "yes" ] && continue
+               # Allow comments.
+               [ -z "${line###*}" ] && continue
+
+               # First endpoint string/regexp match is sufficient for us.
+               cval=`echo $line | cut -s -d' ' -f1`
+               if [ -z "${1##$cval}" ]; then
+                       found=yes
+                       # Don't be too smart at the moment, just be able to handle
+                       # the known-good case ;-)
+
+                       username=`echo $line | cut -s -d' ' -f5`
+                       [ "$username" != "-" ] && echo $username > $CMDS1
+                       # access password
+                       echo $line | cut -s -d' ' -f6 >> $CMDS1
+                       enable_password=`echo $line | cut -s -d' ' -f7`
+                       [ "$enable_password" != "-" ] && {
+                               echo en >> $CMDS1
+                               echo $enable_password >> $CMDS1
+                       }
+                       # same for ports
+                       cat "$CMDS1" > "$CMDS2"
+                       # ...and MAC addresses
+                       cat "$CMDS1" > "$CMDS3"
+                       break
+               fi
+       done < "$MYDIR/cisco.secrets.php"
+       [ "$found" = "yes" ] && return
+       echo "E!connector could not find credentials for $1" >> "$FILE2"
+       exit 3
+}
+
+prepare_fetch_commands()
+{
+       printf 'term len 0\nshow vlan brief\nquit\n' >> $CMDS1
+       printf 'term len 0\nshow int status\nquit\n' >> $CMDS2
+       printf 'term len 0\nshow mac-address-table\nquit\n' >> $CMDS3
+}
+
+prepare_push_commands()
+{
+       printf 'term len 0\nconf t\n' >> $CMDS1
+       while read portname vlanid; do
+               if [ -z "$portname" -o -z "$vlanid" ]; then
+                       echo "E!could not parse input in connector" >> "$FILE2"
+                       continue
+               fi
+               if [ "$vlanid" = "trunk" ]; then
+                       echo "E!trunking is not allowed" >> "$FILE2"
+                       continue
+               fi
+               printf "int $portname\n" >> $CMDS1
+               if [ $vlanid -lt 4096 ]; then
+                       printf "no description\n" >> $CMDS1
+               else
+                       printf "descr VLAN$vlanid\n" >> $CMDS1
+               fi
+               "$MYDIR/vlandecoder" $vlanid >> $CMDS1
+               printf "exit\n" >> $CMDS1
+               echo "C!64!$portname!$ENDPOINT!$vlanid" >> "$FILE2"
+       done < "$FILE1"
+       printf "end\nwri\nquit\n" >> $CMDS1
+}
+
+do_fetch()
+{
+       local tmp_ifname tmp_ifdescr tmp_status tmp_vlanid
+       nc $ENDPOINT 23 < $CMDS1 > "$OUT1"
+       if fgrep -q '% Bad passwords' "$OUT1"; then
+               echo "E!password mismatch while trying to connect to $ENDPOINT" >> "$FILE2"
+               exit 4
+       fi
+       nc $ENDPOINT 23 < $CMDS2 > "$OUT2a"
+       nc $ENDPOINT 23 < $CMDS3 > "$OUT3"
+       cat "$OUT1" | fgrep ' active    ' | sed $SEDFLAG 's/^([[:digit:]]+)[[:space:]]+(.+)[[:space:]]+active    (.*)/\1=\2/;s/[[:space:]]+$//' > $FILE1
+       # Add trunk data, if appropriate.
+       [ -s "$MYDIR/vlantable" ] && cat "$MYDIR/vlantable" >> $FILE1
+
+       # First extract structured info about VLAN membership, then map
+       # special descriptions into VLAN IDs.
+       cat "$OUT2a" | egrep '^(Et|Fa|Gi|Te)' | sed $SEDFLAG 's/[~%]/__RTTMP_percent_sign__/g;s/^([A-Za-z/0-9]+) +(.*) +(connected|notconnect|disabled|err-disabled|monitoring|suspended) +/\1~\2%\3%/;s/%(trunk|routed|([0-9]+)) .*$/%\1/;s/%(monitoring|suspended)%/%connected%/;s/%(err-disabled)%/%disabled%/;s/ +%/%/;s/~/%/' > $OUT2b
+       while read line; do
+               tmp_ifname=`echo $line | cut -d% -f1`
+               tmp_ifdescr=`echo $line | cut -d% -f2`
+               tmp_status=`echo $line | cut -d% -f3`
+               tmp_vlanid=`echo $line | cut -d% -f4`
+               # If the port has a description pretending to be a martian VLAN, map it onto the VLAN ID.
+               if [ -n "$tmp_ifdescr" -a -z "${tmp_ifdescr##VLAN*}" ]; then
+                       tmp_vlanid=${tmp_ifdescr##VLAN}
+               fi
+               echo "$tmp_ifname=$tmp_status,$tmp_vlanid" >> $FILE2
+       done < $OUT2b
+       # FIXME
+       # Here we need to distinguish between different platforms and IOS version,
+       # cause they produce output in different formats.
+       if [ "$SW" = "Cisco+IOS+12.0" ]; then
+               cat "$OUT3" | LC_ALL=C tr -d '\r' | fgrep Dynamic | sed $SEDFLAG 's/ +Dynamic +([0-9]+) +(.+)/=\1@\2/;s/FastEthernet/Fa/;s/GigabitEthernet/Gi/' > "$FILE3"
+       elif [ "$SW" = "Cisco+IOS+12.2" -o "$SW" = "Cisco+IOS+12.1" ]; then
+               case "$HW" in
+                       Cisco+Catalyst+35*|Cisco+Catalyst+37*|Cisco+Catalyst+29*)
+                               cat "$OUT3" | LC_ALL=C tr -d '\r' | egrep 'STATIC|DYNAMIC' | \
+                               sed $SEDFLAG 's/ +([0-9]+|All) +(.+)    (DYNAMIC|STATIC) +(.+)/\2=\1@\4/;s/FastEthernet/Fa/;s/GigabitEthernet/Gi/' > "$FILE3"
+                       ;;
+                       Cisco+Catalyst+49*)
+                               cat "$OUT3" | LC_ALL=C tr -d '\r' | fgrep dynamic | \
+                               sed $SEDFLAG 's/ +([0-9]+) +([0-9a-f\.]+)   dynamic ip +([a-zA-Z/0-9]+) */\2=\1@\3/;s/FastEthernet/Fa/;s/GigabitEthernet/Gi/;s/TenGi/Te/' > "$FILE3"
+                       ;;
+               esac
+       fi
+}
+
+do_push()
+{
+       nc $ENDPOINT 23 < $CMDS1 >/dev/null
+}
+
+remove_tempfiles()
+{
+       [ -f "$CMDS1" ] && rm -f "$CMDS1"
+       [ -f "$CMDS2" ] && rm -f "$CMDS2"
+       [ -f "$CMDS3" ] && rm -f "$CMDS3"
+       [ -f "$OUT1" ] && rm -f "$OUT1"
+       [ -f "$OUT2a" ] && rm -f "$OUT2a"
+       [ -f "$OUT2b" ] && rm -f "$OUT2b"
+       [ -f "$OUT3" ] && rm -f "$OUT3"
+}
+
+create_tempfiles()
+{
+       # This one is for VLAN list.
+       CMDS1=`mktemp /tmp/cisco.connector.XXXX`
+       # And this one holds ports list...
+       CMDS2=`mktemp /tmp/cisco.connector.XXXX`
+       # ...and one more for MAC address table
+       CMDS3=`mktemp /tmp/cisco.connector.XXXX`
+       # The following are buffers to hold the whole switch output
+       # before filtering.
+       OUT1=`mktemp /tmp/cisco.connector.XXXX`
+       OUT2a=`mktemp /tmp/cisco.connector.XXXX`
+       OUT2b=`mktemp /tmp/cisco.connector.XXXX`
+       OUT3=`mktemp /tmp/cisco.connector.XXXX`
+       [ -f "$CMDS1" -a -f "$CMDS2" -a -f "$CMDS3" -a -f "$OUT1" -a -f "$OUT2a" -a -f "$OUT2b" -a -f "$OUT3" ] && return
+       echo "E!connector cannot create tempfiles" >> "$FILE2"
+       remove_tempfiles
+       exit 5
+}
+
+case $COMMAND in
+       fetch)
+               create_tempfiles
+               prepare_connect_commands $ENDPOINT
+               prepare_fetch_commands
+               do_fetch
+               remove_tempfiles
+       ;;
+       push)
+               create_tempfiles
+               prepare_connect_commands $ENDPOINT
+               prepare_push_commands
+               do_push
+               remove_tempfiles
+       ;;
+       *)
+               echo "E!unknown command for connector" >> "$FILE2"
+               exit 6
+       ;;
+esac
+
+exit 0
diff --git a/gateways/deviceconfig/cisco.secrets.php-sample b/gateways/deviceconfig/cisco.secrets.php-sample
new file mode 100644 (file)
index 0000000..dfa18cd
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+echo "Unauthorized access attempt has been logged. Cheers.";
+exit();
+/*
+
+# Syntax:
+# <endpoint|*> <telnet> <hostname|-> <port|-> <username|-> <line password> <enable password>
+# FIXME: <endpoint|*> <rsh> <username>
+
+# S-T-A-R-T
+switch1 telnet - - - password2 enablepassword
+switch2 telnet - - - password enablepassword
+switch3 telnet - - username3 password3 enablepassword3
+# S-T-O-P
+
+*/
+?>
diff --git a/gateways/deviceconfig/main b/gateways/deviceconfig/main
new file mode 100755 (executable)
index 0000000..6399abd
--- /dev/null
@@ -0,0 +1,239 @@
+#!/bin/sh
+
+# This is a RackTables gateway for changing switch ports membership
+# across VLANs. It works accordingly to the gateway protocol described
+# in gateways.php and accepts the following commands on its stdin:
+#
+# * connect: connect to a switch, fetch all necessary data, store and
+#   disconnect
+#
+# * listvlans: list all VLANs found on the switch, propably filtering
+# out those administratively prohibited. Only the VLANs from this
+# list will be allowed as new destination for 'set' command.
+#
+# * listports: list all ports on the switch and their current status.
+#   Untagged (switchport mode access) ports will be shown with their
+#   VLAN ID and tagged ports will be shown as 'trunk' regardless of
+#   how many VLANs they are members of.
+#
+# * listmacs: output unsorted list of all dynamically learned MAC
+#   addresses present on the switch
+#
+
+endpoint=
+hw=
+sw=
+user=
+handler=
+CONNECTED=0
+MYDIR=`dirname $0`
+
+decode_error()
+{
+       case "$1" in
+               0)
+                       echo -n 'success'
+               ;;
+               1)
+                       echo -n 'internal error 1'
+               ;;
+               2)
+                       echo -n 'internal error 2'
+               ;;
+               3)
+                       echo -n 'password not found'
+               ;;
+               4)
+                       echo -n 'invalid password'
+               ;;
+               5)
+                       echo -n 'cannot create temporary files'
+               ;;
+               6)
+                       echo -n 'invalid command'
+               ;;
+               7)
+                       echo -n 'unknown host OS'
+               ;;
+               *)
+                       echo -n 'unknown error'
+               ;;
+       esac
+}
+
+# Not only connect, but gather all the data at once and remember the context.
+do_connect()
+{
+       endpoint=`echo $args | cut -s -d' ' -f1`
+       hw=`echo $args | cut -s -d' ' -f2`
+       sw=`echo $args | cut -s -d' ' -f3`
+       user=`echo $args | cut -s -d' ' -f4`
+       # sanity checks
+       if [ -z "$endpoint" -o -z "$hw" -o -z "$sw" -o -z "$user" ]; then
+               echo 'ERR!too few arguments to connect'
+               return
+       fi
+       case "$sw" in
+               Cisco+IOS+12.0|Cisco+IOS+12.1|Cisco+IOS+12.2)
+                       handler=cisco
+               ;;
+               *)
+                       echo "ERR!Don't know how to handle $sw on $endpoint"
+                       return
+               ;;
+       esac
+
+       # prepare temp files
+       PORTINFO=`mktemp /tmp/racktables.XXXX`
+       if ! [ -f "$PORTINFO" ]; then
+               echo 'ERR!could not create portinfo tmpfile'
+               return
+       fi
+       VLANINFO=`mktemp /tmp/racktables.XXXX`
+       if ! [ -f "$VLANINFO" ]; then
+               echo 'ERR!could not create vlaninfo tmpfile'
+               rm -f "$PORTINFO"
+               return
+       fi
+       MACINFO=`mktemp /tmp/racktables.XXXX`
+       if ! [ -f "$MACINFO" ]; then
+               echo 'ERR!could not create MACinfo tmpfile'
+               rm -f "$PORTINFO" "$VLANINFO"
+               return
+       fi
+
+       # get the data
+       "$MYDIR/$handler.connector" $endpoint $hw $sw fetch "$VLANINFO" "$PORTINFO" "$MACINFO"
+       ret=$?
+       if [ $ret = 0 ]; then
+               CONNECTED=1
+               echo "OK!connected to $endpoint";
+       else
+               echo -n "ERR!Cannot connect to $endpoint ("
+               decode_error $ret
+               echo ')'
+       fi
+}
+
+do_listfile()
+{
+       local F=$1
+       if ! [ -f "$F" ]; then
+               echo "ERR!Lost temp file '$F' on the way"
+               return
+       fi
+       echo -n 'OK!'
+       local semicolon=''
+       # tr might do the work, but use our chance to perform filtering once more
+       cat "$F" | while read line; do
+               [ "$line" = "" ] && continue
+               echo -n "$semicolon$line"
+               semicolon=';'
+       done
+       echo
+}
+
+do_set()
+{
+       # sanity checks
+       local setline=$1
+       if [ -z "$setline" ]; then
+               echo 'ERR!missing set argument'
+               return
+       fi
+       local REQUESTS=`mktemp /tmp/racktables.XXXX`
+       local REPLIES=`mktemp /tmp/racktables.XXXX`
+       echo $1 | tr ';' '\n' | while read setexpr; do
+               portname=`echo $setexpr | cut -s -d'=' -f1`
+               newvlan=`echo $setexpr | cut -s -d'=' -f2`
+               curvlan=`egrep "^$portname=" $PORTINFO | cut -s -d'=' -f2 | cut -d',' -f2`
+               if [ -z "$curvlan" ]; then
+                       echo "C!167!$portname" >> "$REPLIES"
+                       continue
+               fi
+               if [ "$curvlan" = "trunk" ]; then
+                       echo "C!168!$portname" >> "$REPLIES"
+                       continue
+               fi
+               [ "$curvlan" = "$newvlan" ] && continue
+               echo "$portname $newvlan" >> "$REQUESTS"
+               cmembers=`grep -c ",$newvlan$" "$PORTINFO"`
+               if [ "$cmembers" = "0" -a $newvlan -lt 4096 ]; then
+                       echo "C!203!$portname!$newvlan" >> "$REPLIES"
+                       echo "C!204" >> "$REPLIES"
+               fi
+       done
+       nr=`egrep -c '^C!1.' "$REPLIES"`
+       if [ "$nr" -ge 1 ]; then
+               echo "C!205!$nr" >> "$REPLIES"
+       fi
+
+       nq=`egrep -c '^.' "$REQUESTS"`
+       if [ "$nq" -ge 1 ]; then
+               # Go!
+               "$MYDIR/$handler.connector" $endpoint $hw $sw push "$REQUESTS" "$REPLIES" "$MACINFO"
+               local ret=$?
+
+               if [ $ret != 0 ]; then
+                       echo "C!169!$endpoint!$ret"
+                       return
+               fi
+               echo "C!63!$nq" >> "$REPLIES"
+       fi
+       echo -n 'OK!'
+       local SEMICOLON=
+       while read reply; do
+               echo -n $SEMICOLON$reply
+               SEMICOLON=';'
+               timestamp=`date '+%Y-%m-%d %H:%M:%S'`
+               [ -w "$MYDIR/changes.log" ] && echo "$timestamp $user@$endpoint $reply" >> "$MYDIR/changes.log"
+       done < "$REPLIES"
+       echo
+       rm -f "$REQUESTS" "$REPLIES"
+}
+
+# main loop
+while read cmd args; do
+       case $cmd in
+               connect)
+                       if [ $CONNECTED = 1 ]; then
+                               echo 'ERR!Already connected'
+                       else
+                               do_connect $args
+                       fi
+                       ;;
+               listvlans)
+                       if [ $CONNECTED = 1 ]; then
+                               do_listfile "$VLANINFO"
+                       else
+                               echo 'ERR!Not connected'
+                       fi
+                       ;;
+               listports)
+                       if [ $CONNECTED = 1 ]; then
+                               do_listfile "$PORTINFO"
+                       else
+                               echo 'ERR!Not connected'
+                       fi
+                       ;;
+               listmacs)
+                       if [ $CONNECTED = 1 ]; then
+                               do_listfile "$MACINFO"
+                       else
+                               echo 'ERR!Not connected'
+                       fi
+                       ;;
+               set)
+                       if [ $CONNECTED = 1 ]; then
+                               do_set $args
+                       else
+                               echo 'ERR!Not connected'
+                       fi
+                       ;;
+               *)
+                       echo "ERR!unknown command $cmd"
+       esac
+done
+
+rm -f "$PORTINFO" "$VLANINFO" "$MACINFO"
+exit 0