#!/bin/bash
########################################################################
#  File...........: qethconf      
#  Author(s)......: Thomas Weber <tweber@de.ibm.com>
#  (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2002
# 
#  History of changes:
#  none
# 
#  This shell script simplifies the usage of the OSA-Express functions    
#
#  - IP address takeover (IPA)                                 
#  - Proxy ARP                                                       
#  - Virtual IP address (VIPA)                           
#
#  IP addresses in IPv4 or IPv6 format must be no longer specified in
#  hexadecimal format. The script compounds the commands for the functions
#  according to the given parameters and sends it to the appropriate
#  OSA-Express device driver /proc file.   
# 
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2, or (at your option)
#  any later version.
# 
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
# 
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
########################################################################
 
version="1.0.0"                          # version of this script
script_name=${0##*/}                     # name of this script
vipa_ext="_vipa"                         # command extension for VIPA
parp_ext="_rxip"                         # command extesnion for Proxy ARP
proc_file="/proc/qeth_ipa_takeover"      # qeth proc file for these features
echo_cmd=""                              # echo command to be build 


#
# exit script on error and print usage hint
#
function __exit_on_error
{
   echo "Try '$script_name --help' for more information."
   exit 1
}


#
# function for printing the usage message
#
function __usage
{
    printf '\n%s %s %s\n\n' "Usage:" $script_name "TYPE CMD [IPADDR] [INTERFACE]"
    printf '  %s %s\n\n' "Version:" $version
    printf '  %s\n'   "Description:"
    printf '  %s\n\n' "Shell script for configuring OSA-Express IPA,VIPA and Proxy ARP"
    printf '  %s\n\n' "Parameter:"
    printf '  %s\t\t%s\n' "TYPE" "<ipa/vipa/parp>"
    printf '\t\t%s\n' "choose one of these keywords for feature"
    printf '\t\t%s\n' "ipa  - IP address takeover"
    printf '\t\t%s\n' "vipa - Virtual IP address"
    printf '\t\t%s\n' "parp - Proxy ARP"
    printf '\n  %s\t\t%s\n' "CMD" "<add/del/inv4/inv6/list>"
    printf '\t\t%s\n' "where"
    printf '\t\t%s\n' "add  - add an IP address or address range"
    printf '\t\t%s\n' "del  - delete an IP address or address range"
    printf '\t\t%s\n' "inv4 - inverts the selection of address ranges for IPv4"
    printf '\t\t%s\n' "       (only IPA)"
    printf '\t\t%s\n' "inv6 - inverts the selection of address ranges for IPv6"
    printf '\t\t%s\n' "       (only IPA)"
    printf '\t\t%s\n' "list - list defined entries per feature"
    printf '\n  %s\t%s\n' "IPADDR" "[-x]<addr>[/<mask_bits>]"
    printf '\t\t%s\n' "required for commands add and del"
    printf '\t\t%s\n' "addr      - IP address in IPv4 or IPv6 format"
    printf '\t\t%s\n' "            e.g. 192.168.10.38 or FE80::800:5A12:3459"
    printf '\t\t%s\n' "            use option -x for hexadecimal format"
    printf '\t\t%s\n' "            e.g. c0a80a26"
    printf '\t\t%s\n' "mask_bits - number of bits which are set in the"
    printf '\t\t%s\n' "            network mask (required for IPA)"
    printf '\n  %s\t%s\n' "INTERFACE" "interface name to which the address or address range"
    printf '\t\t%s\n' "is bound, e.g eth0 (this parameter is optional for IPA)"
    printf '\n%s\n' "Report bugs to <linux390@de.ibm.com>"
    exit 1
}


#
# prints a row from /proc/qeth_ipa_takeover
#
function __print_line
{
   local ip_num=

   if [ -n "$(echo $raw_cmd | grep '4')" ]; then  # IPv4
      
      # convert hex IPv4 address to human format
      for i in 0 2 4 6; do
        # convert hex digits to uppercase (bc needs this)
        # use bc for hex to dec conversion
        ip_num=`echo ${ipinfo:i:2} | tr '[a-f]' '[A-F]'`  
        ip_num=`echo "ibase=16; $ip_num" | bc`

        fmt_line="$fmt_line$ip_num"        

        if [ "$i" -ne 6 ]; then
           fmt_line="$fmt_line."
        fi
      done 

   else # IPv6
     
      # convert IPv6 address to human readable format
      for i in 0 4 8 12 16 20 24 28; do
        
        ip_num=${ipinfo:i:4} 

        fmt_line="$fmt_line$(echo $ip_num | sed -e 's/0000/0/')"

        if [ "$i" -ne 28 ]; then
           fmt_line="$fmt_line:"
        fi
      done
   fi

   # add mask bits and interface, if existent
   if [ -n "$(echo $ipinfo| grep '/')" ]; then
      fmt_line="$fmt_line/$(echo ${ipinfo##*/} | sed -e 's/:/ /')"
   elif [ -n "$(echo $ipinfo| grep ':')" ]; then
      fmt_line="$fmt_line ${ipinfo##*:}"
   fi

   # finally echo line to stdout
   echo $fmt_line
}


#
# list defined entries per IPA/VIPA/PARP function
#
function __list_entries
{
  local cmd_type=$1
  local fmt_line=

  declare -i count=0

  while read raw_cmd ipinfo; do

     case "$cmd_type" in
       ipa  ) if [ -n "$(echo $raw_cmd | grep 'add4')" ] || \
                 [ -n "$(echo $raw_cmd | grep 'add6')" ]; then
                 fmt_line="$cmd_type add " 
                 __print_line
                 count=count+1
              fi;;
       vipa ) if [ -n "$(echo $raw_cmd | grep $vipa_ext)" ]; then
                 fmt_line="$cmd_type add "
                 __print_line
                 count=count+1
              fi;;
       parp ) if [ -n "$(echo $raw_cmd | grep $parp_ext)" ]; then
                 fmt_line="$cmd_type add "
                 __print_line 
                 count=count+1
              fi;;
     esac

  done < $proc_file

  if [ "$count" -eq 0 ]; then 
     echo $script_name": currently no $cmd_type entries defined"
  fi

  exit 0
}


#
# IP address conversion - converts IP address to hexadecimal format
#
function __ip_conv
{
    declare -i count=0
    declare -i zeropad=8
    ip_temp=
    ip_shortened="false"
    strt_end_col="false"

    ipv4_rule='^[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}\.[[:digit:]]\{1,3\}$'
    ipv6_rule='^[[:xdigit:]:.]\+$'

    # check if IP address is given as hex input; option -x 
    # if yes, do not need to convert IP address
    # otherwise convert IP address to hex
    if [ -n "$(echo $ip_addr | grep '\-x')" ]; then
       #
       # hex input given 
       #
       ip_addr=${ip_addr##-x}
                                   
       if [ -n "$(echo $ip_addr | grep '^[[:xdigit:]]\{8\}$')" ]; then 
          ip_ver=4
       elif [ -n "$(echo $ip_addr | grep '^[[:xdigit:]]\{32\}$')" ]; then
          ip_ver=6
       else
          echo $script_name": bad IP address"
          __exit_on_error
       fi
       ip_hex=$ip_addr
    else
       #
       # IPv4 format
       #
       if [ -n "$(echo $ip_addr | grep "$ipv4_rule")" ]; then
          ip_ver=4
          until [ -z "$(echo $ip_addr | grep '\.')" ]; do
            ip_temp="${ip_addr%%.*}" 
            ip_temp=$(echo $ip_temp | sed -e 's/0*//') 
            if [ -z "$ip_temp" ]; then
               ip_hex=$ip_hex"00"
            else
               ip_hex="$ip_hex$(printf '%02x' $ip_temp)"
            fi
            ip_addr=${ip_addr#*.}
          done
          ip_temp=$(echo $ip_addr | sed -e 's/0*//')
          if [ -z "$ip_temp" ]; then
             ip_hex=$ip_hex"00"
          else
             ip_hex="$ip_hex$(printf '%02x' $ip_temp)"
          fi
                                                     
          if [ "${#ip_hex}" -gt 8 ]; then
             echo $script_name": bad IPv4 address" 
             __exit_on_error
          fi
       #
       # IPv6 format
       #
       elif [ -n "$(echo $ip_addr | grep "$ipv6_rule")" ]; then

          # check for IPv4-compatible address or IPv4-mapped address
          if [ -n "$(echo $ip_addr | grep '\.')" ]; then
             ipv4_part=${ip_addr##*:}
             ipv6_part=`echo ${ip_addr%:*} | tr '[a-f]' '[A-F]'`":"
             if [ -z "$(echo $ipv4_part | grep "$ipv4_rule")" ]; then
                echo $script_name": bad IP address"
                __exit_on_error
             fi
             ip_temp="$(printf '%02x' ${ipv4_part%%.*})"
             ipv4_part=${ipv4_part#*.}
             ip_temp=$ip_temp"$(printf '%02x' ${ipv4_part%%.*})"
             ipv4_part=${ipv4_part#*.}
             ip_temp=$ip_temp":""$(printf '%02x' ${ipv4_part%%.*})"
             ipv4_part=${ipv4_part#*.}
             ip_temp=$ip_temp"$(printf '%02x' $ipv4_part)"
             if [ "${#ip_temp}" -gt 9 ]; then
                echo $script_name": bad IPv6 address"
                __exit_on_error
             fi
             ip_addr=$ipv6_part$ip_temp
          fi

          # count number of colons within IP address 
          ip_temp=$ip_addr
          until [ -z "$(echo $ip_temp | grep ':')" ]; do
             ip_temp=${ip_temp#*:}
             count=count+1
          done
                          
          # test number of allowed colons                               
          if [ "$count" -gt 7 ] || [ "$count" -lt 2 ]; then
             echo $script_name": bad IPv6 address"
             __exit_on_error
          fi 

          # 
          # have to increase count for zero padding for starting/ending colon
          #
          if [ -z "${ip_addr%::*}" ]; then
             zeropad=zeropad+1
             strt_end_col="true"
          fi
          if [ -z "${ip_addr#*::}" ]; then
             zeropad=zeropad+1
             strt_end_col="true"
          fi 

          #
          # loop through IPv6 address and convert it to 16 byte hex input
          #
          ip_ver=6
          ip_addr=$ip_addr":"          # use colon as end marker here

          while [ -n "$(echo $ip_addr | grep ':')" ]; do
             ip_temp=${ip_addr%%:*}
 
             if [ -z "$ip_temp" ]; then
                # found IPv6 double colon shortcut - add missing 0s
                if [ "$ip_shortened" = false ]; then
                   zeropad=zeropad-$count                     
                   while [ $zeropad -ne 0 ]; do
                      ip_hex=$ip_hex"0000"
                      zeropad=zeropad-1
                   done
                   ip_shortened="true"
                   if [ $strt_end_col = "true" ]; then
                      ip_addr=${ip_addr#:}
                   fi
                elif [ ! $ip_addr = ":" ]; then
                   echo $script_name": IPv6 double colon shortcut can be used only once"
                   __exit_on_error
                fi
             fi

             case "${#ip_temp}" in 
                 1 )  ip_hex=$ip_hex"000"$ip_temp ;;
                 2 )  ip_hex=$ip_hex"00"$ip_temp ;;
                 3 )  ip_hex=$ip_hex"0"$ip_temp ;;
                 4 )  ip_hex="$ip_hex$ip_temp" ;; 
                 0 )  ;;
                 * )  echo $script_name": bad IPv6 address"
                      __exit_on_error ;;
             esac
 
             ip_addr=${ip_addr#*:}
          done

          #
          # test if given IPv6 address was too short
          #
          if [ "$count" -lt 7 -a "$ip_shortened" = false ]; then
            echo $script_name": given IPv6 is too short"
            __exit_on_error  
          fi

       else 
          echo $script_name": IP address wrong or missing"
          __exit_on_error
       fi   
    fi             
}


#
# builds echo command according to the given parameters 
# 
function __build_cmd
{
    # input parameters
    local cmd_type=$1 
    local cmd_parm=$2 
    local ip_addr=$3
    local interface=$4 

    local mask_bits=
    local ip_ver=
    local ip_hex=

    #
    # allow also shortcut rxip for Proxy ARP
    #
    if [ $cmd_type = rxip ]; then
       cmd_type=parp
    fi

    #
    # check if mask bits are given for parameter IPADDR 
    #
    if [ -z "$ip_addr" ]; then
        echo $script_name": IP address parameter missing"
        __exit_on_error
    elif [ -n "$(echo $ip_addr | grep '/')" ]; then
       if [ $cmd_type = ipa ]; then 
          mask_bits=${ip_addr##*/}
          ip_addr=${ip_addr%%/*}
          if [ -z "$(echo $mask_bits | grep '^[[:digit:]]\{1,3\}$')" ]; then
             echo $script_name": invalid mask bits specified"
             __exit_on_error
          fi    
       else
          echo $script_name": mask bits not allowed for $cmd_type"
          __exit_on_error
       fi
    elif [ $cmd_type = ipa ]; then
       echo $script_name": mask bits required for function $cmd_type"
       __exit_on_error
    fi

    #
    # check interface name if specified 
    #
    if [ -z "$interface" ] && [ $cmd_type = parp -o $cmd_type = vipa ]; then
       echo $script_name": interface name required for function $cmd_type"
       __exit_on_error
    elif [ -n "$interface" ] && \
       [ -z "$(echo $interface | grep '^[[:alnum:]]\+$')" ]; then
       echo $script_name": invalid interface name specified"
       __exit_on_error
    fi 

    #
    # IP address conversion needed
    #
    __ip_conv 

    #
    # assemble command
    #
    echo_cmd="$echo_cmd$ip_ver $ip_hex"
    if [ -n "$mask_bits" ]; then
       echo_cmd="${echo_cmd}/$mask_bits"
    fi
    if [ -n "$interface" ]; then
       echo_cmd="${echo_cmd}:$interface"
    fi
    
    #
    # look for appropriate row in the qeth conf file, if del is specified
    #
    if [ $cmd_parm = del ] && \
       ! grep -sq "${echo_cmd##*del}" $proc_file; then
          echo $script_name": no such $cmd_type entry found."
          exit 0     
    fi    
}


#---------------------------------------
# --           main                   --
#---------------------------------------

#
# parse options (currently none avail)  
#
while [ -n "$(echo $1 | grep '-')" ]; do
    case "$1" in
       *  ) __usage ;;               
    esac
    shift
done

#
# check if target proc file exists - 
# otherwise the OSA-E device driver is not loaded
#
if [ ! -e "$proc_file" ]; then
   echo $script_name": OSA-Express device driver not loaded" 
   echo "Try 'man $script_name' for more information."
   exit 1
fi

#
# parse arguments TYPE and CMD
#
if [ "$#" -lt 2 -o "$#" -gt 4 ]; then
    echo $script_name": invalid number of parameters specified"
    __exit_on_error
else
   case "$1" in
       ipa  ) case "$2" in
                  add | del       ) echo_cmd="$2"
                                    __build_cmd "$@";;
                  inv4 | inv6     ) if [ "$#" -gt 2 ]; then
                                       echo $script_name": too many parameters for $2 command"           
                                       __exit_on_error
                                    else
                                       echo_cmd="$2" 
                                    fi;;
                  list            ) if [ "$#" -gt 2 ]; then
                                       echo $script_name": too many parameters for $2 command"
                                       __exit_on_error
                                    else
                                       __list_entries "$@"
                                    fi;; 
                  *               ) echo $script_name": invalid CMD parameter specified"
                                    __exit_on_error ;; 
              esac ;;
       vipa ) case "$2" in
                  add | del ) echo_cmd="$2$vipa_ext" 
                              __build_cmd "$@";;
                  list      ) if [ "$#" -gt 2 ]; then
                                 echo $script_name": too many parameters for $2 command"
                                 __exit_on_error
                              else
                                 __list_entries "$@"
                              fi;;
                  *         ) echo $script_name": invalid CMD parameter specified"
                              __exit_on_error ;;
              esac;;
       parp | rxip ) case "$2" in
                  add | del ) echo_cmd="$2$parp_ext"    
                              __build_cmd "$@";;
                  list      ) if [ "$#" -gt 2 ]; then
                                 echo $script_name": too many parameters for $2 command"
                                 __exit_on_error
                              else
                                  __list_entries "$@"
                              fi;;
                  *         ) echo $script_name": invalid CMD parameter specified" 
                              __exit_on_error ;;
              esac;;
       * ) echo $script_name": invalid TYPE parameter specified" 
           __exit_on_error ;;
   esac
fi

#
# finally echo cmd to appropriate OSA-Express proc file
# and check if command was successful
#
echo $echo_cmd > $proc_file

case "$2" in
    add )  if grep -sqx "$echo_cmd" $proc_file ; then
              echo "$script_name: $1 add command successful."
           else
              echo "$script_name: $1 add command unsuccessful."
              echo "See qeth kernel messages for possible reason."
              echo "Tried the following command:"
              echo "echo $echo_cmd > $proc_file"
           fi;;
    del )  echo "$script_name: $1 del command executed successfully.";;

    inv4 ) echo "$script_name: any IPv4 address ranges are removed from IPA takeover mechanism.";;
    inv6 ) echo "$script_name: any IPv6 address ranges are removed from IPA takeover mechanism.";;
esac
