# This is a shell fragment containing a bunch of
# routines useful for setting up IPv6 networking,
# including 6to4 xunnels.

# Terminology:  A xunnel is the endpoint of a tunnel.  A working
# tunnel needs two or more xunnels.  Typically each xunnel is on
# a different host.

## Some defaults:
: ${ip_addr_oracle:=http://www.av8n.com/cgi-bin/ipaddr}
# was: www.ipaddressworld.com

ip4patt='[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
ip6patt='[0-9a-f]*:[0-9a-f]*:[0-9a-f]*' # at least two colons

# Just like plain-old "mapfile -t", except takes multiple arguments.
# Each arg (except the last) gets one line
# the last arg gets all the remaining lines,
# separated by '|' marks
# (which is sorta like the 'read' builtin, and sorta like perl)
#
# if the first argument is -q, the lines will be quoted
# to protect them from shell expansion
function MapFile() {
  local arg line quoted fmt
  fmt=%s
  if test _"$1" = _"-q" ; then
    fmt=%q
    shift
  fi
  if (($# == 0)) ; then
    1>&2 echo "Bad call to MapFile"
    exit 1
  fi
  while true ; do
    arg=$1 ; shift
    if (($# != 0)) ; then
      read foo || true
      printf -v "$arg" $fmt "$foo"
    else
      # here if arg is the *last* arg
      sep=''
      rslt=''
      while read line ; do
        rslt="$rslt$sep$line"
        sep='|'
      done
      printf -v "$arg" "$rslt"
      break
    fi
  done
  true
}


# Map a device name to a small integer
# based on the kernel's enumeration of devices.
# We use this as the subnet number (aka the SLA ID number).
function get_devnum() {
  local devnum
  if test -z "$1" ; then
    1>&2 echo "get_devnum requires an argument (ifc, e.g. eth0)"
    return 1
  fi
  devnum=$( ip addr ls dev $1 | sed 's/\([0-9]*\)[: ].*/\1/;q') || return 1
  if test -z "$devnum" ; then
    return 1	## error msg already printed by "ip addr"
  fi
  echo $devnum
}

# Obtain MAC info by parsing the output of
# the 'ifconfig' program.
# Call with $1 = device name such as eth0
# Result is formatted as six two-digit hex numbers,
# e.g.  00 27 13 b4 b2 90
function get_mac(){
  local line arg
  if test -z "$1" ; then
    1>&2 echo "get_mac requires an argument (ifc, e.g. eth0)"
    return 1
  fi
  2>/dev/null ifconfig $1 | while read line ; do
    set -- $line
    while test -n "$*" ; do
      arg=$1 ; shift
      if test "_$arg" = "_HWaddr" ; then
	echo $1 | sed 's/[-:]/ /g' -
	return 99               ## successful early exit from do-loop
      fi
    done
  done      ## falling off the end of the loop == failure
  if test "$?" -eq 99 ; then return 0 ; fi
  return 1
}

# Print the EUI-64 identifier based on the MAC address
# of some device.
# Call with arguments = device names to try.
# The winner is the first one that has an EUI
# Note that devices such as "lo" and "6to4" don't have a EUI.
# Note that the "dummy" device appears to have an EUI,
#  but I have no idea where it comes from.
#
# If none of the devices mentioned in $* have a EUI,
# we make something up: we return dead:f1ea:$devnum:0000
#
# The bit that says "locally administered" is forced to be ON.
#
function get_eui() {
  local mac first

# Reference:
#   http://en.wikipedia.org/wiki/Organizationally_Unique_Identifier
  local LOCAL_ADM=2             # "local/universal" bit

  mac=$(
    for dev in $* ; do
      if get_mac $dev  ; then break ; fi
    done
  )

  if test -z "$mac" ; then
    printf "dead:f1ea:%04x:0000\n" $( get_devnum $1 )
  else
    set -- $mac
    ((first=0x$1 | LOCAL_ADM))
    printf "%02x%02x:%02x%02x:%02x%02x:%02x%02x\n" \
      $first 0x$2 0x$3 0xff 0xfe 0x$4 0x$5 0x$6
  fi
  return 0
}


# Get outgoing network device for ipv4 traffic.
# (Could be eth1 or wlan0 or something like that.)
#
function get_odev4(){
  local addr stuff key oracle_host
  oracle_host=$( echo $ip_addr_oracle \
     | sed 's![^/]*//\([^/]*\).*!\1!' )
  addr=$( resolve4 $oracle_host ) || return 1
  if test -z "$addr" ; then
    addr=192.3.1.1        # arin.net
  fi
  stuff=$( ip route get to $addr ) || return 2
  set -- $stuff
  while test -n "$*" ; do
    key=$1; shift
    if test "_$key" = "_dev" ; then
      echo $1
      break
    fi
  done
}

function geturl() {
  local prog
  : ${prog:=$(type -p curl)}
  : ${prog:=$(type -p wget)}
# disallow lynx, because it doesn't accept -6
  : ${prog:=$(type -p lynx---nope)}
  : ${prog:=none}

  case $prog in
    none)
    # oh dear, no working url-getters
      1>&2 echo "You need to install 'curl', 'wget', or 'lynx'."
      exit 2
      ;;
    */curl)
      echo "$prog -s"
      ;;
    */wget)
      echo "$prog -q -O -"
      ;;
    */lynx)
      echo "$prog -source"
      ;;
    *)
      1>&2 echo "geturl: should never happen ($prog)"
      exit 1
  esac
  return
}

# Print (on stdout) our IP address as seen by the outside world.
#
# If trouble, print error msg on stderr, print nothing on stdout,
# and return an error status.
function get_WANip4() {
  ###### 1>&2 echo "get_WANip4"
  local x geturl
  geturl=$( geturl ) || exit 2

# Useful for testing, sometimes:
#    TEST_IP=68.231.191.153
  if test -n "$TEST_IP" ; then
    echo "$TEST_IP"
  else
    #debug# 1>&2 echo  "$geturl http://www.av8n.com/cgi-bin/ipaddr/ip-conf"
    $geturl http://www.av8n.com/cgi-bin/ipaddr/ip-conf
  fi
}

function get_WANip6() {
  local x geturl
  geturl=$( geturl ) || exit 2

  if test -n "$TEST_IP" ; then
    echo "$TEST_IP"
  else
    #debug# 1>&2 echo  "$geturl http://www.av8n.com/cgi-bin/ipaddr/ip-conf"
    $geturl http://ipv6.av8n.com/cgi-bin/ipaddr/ip-conf
  fi
}

# Print (to stdout) numerical ip4addr and ip6addr.
# If WANip4 already defined, convert to numerical;
# otherwise figure it out empirically from scratch.
# If any errors, print msg and return error code.
# Call with $1 = known WANip4 if any,
#   (possibly in non-numeric symbolic form).
function fancy_get_WAN_addr() {
  #### 1>&2 echo fancy "'$1'"
  local WANip4=$1 WANip6
  if test -z "$WANip4" ; then
    if ! WANip4=$( get_WANip4 ) ; then
      1>&2 echo oops
      return 1
    else
      :
      #####1>&2 echo "got $WANip4"
    fi
  else  # not fetched;  already defined, probably by -W
    if ! WANip4=$( resolve4 $WANip4 )  ; then
      1>&2 echo "Could not resolve '$WANip4' to an IPv4 address."
      1>&2 echo "Try $0 -h for help."
      return 1
    fi
  fi

  if test -z "$WANip6" ; then
    if ! WANip6=$( get_WANip6 ) ; then
      1>&2 echo oops
      return 1
    else
      :
      ##### 1>&2 echo "got $WANip6"
    fi
  else  # not fetched;  already defined, probably by -W
    if ! WANip6=$( resolve4 $WANip6 )  ; then
      1>&2 echo "Could not resolve '$WANip6' to an IPv6 address."
      1>&2 echo "Try $0 -h for help."
      return 1
    fi
  fi

  ##### 1>&2 echo returning "'$WANip4'" "'$WANip6'"
  echo $WANip4
  echo $WANip6
}


function tld_dig() {
  local tldserver host rslt
  tldserver=$1
  host=${2%.}.
  rslt=$( dig @$tldserver $host | \
           while read hname time proto rectype addr junk ; do
    if test "_$host" = "_$hname" -a "_A" = "_$rectype" -a "_IN" = "_$proto" ; then
      echo $addr
      break
    fi
  done )
  if test -z "$rslt" ; then return 1 ; fi
  echo "$rslt"
}

function resolve4() {
  local arg=$1;
  if echo "$arg" | egrep -q "^$ip4patt$" ; then
    echo $1		# already a dotted quad
    return 0
  fi
  set -- $( dig +short $arg a )
# Loop over answers, skipping cruft such as CNAMEs
  while test -n "$*" ; do
    local rslt=$1 ; shift
    if echo "$rslt" | egrep -q "^$ip4patt$" ; then
      echo $rslt	# found a dotted quad
      return 0
    else
      1>&2 echo no match "'$rslt'" "'$ip4patt'"
    fi
  done
  return 1	# no dotted quads found.
}

function resolve6() {
  local arg=$1;
  if echo "$arg" | egrep -qi "^$ip6patt" ; then
    echo $1		# already looks like an address
    return 0
  fi
  set -- $( dig +short $arg aaaa )

# Loop over answers, skipping cruft such as CNAMEs
  while test -n "$*" ; do
    local rslt=$1 ; shift
    if echo "$rslt" | egrep -qi "^$ip6patt" ; then
      echo $rslt	# found an ipv6 address
      return 0
    else
      1>&2 echo no match "'$rslt'" "'$ip4patt'"
    fi
  done
  return 1	# no dotted quads found.
}

if false ; then
  resolve6 av8n.com || exit $?
  resolve4 av8n.com || exit $?
fi

# prints all the nameservers found in /etc/resolv.conf
function get_resolv_conf_nameservers() {
  local keyword addr junk
  cat /etc/resolv.conf | (
    while read keyword addr junk ; do
      if test "_$keyword" = "_nameserver" ; then
        echo "$addr"
      fi
    done
  )
}

# Extract from routing tables and print:
# 1) the IPv6 address that is (probably?)
#  the address we should register with DNS, followed by
# 2) the Ipv4 address, followed by
# 3) the device name for ipv6 traffic (e.g. eth0).
#
# Or ... if we can't find a good IPv6 address;
# print nothing, and return with error status=1.
function get_our_ipaddrs() {
  local dest key dev6 src6 src4 junk
  local geturl
  geturl=$( geturl ) || exit 2

# some random meaningless IP address here:
  array=( $( ip -6 route get 1:: ) )
  while (( ${#array[@]} > 0 )) ; do
    verb="${array[0]}" ;  array=( "${array[@]:1}" )
    case $verb in
      dev)
        dev6="${array[0]}" ;  array=( "${array[@]:1}" )
        ;;
      src)
        outside="${array[0]}" ;  array=( "${array[@]:1}" )
        ;;
    esac
  done

  src6="$outside"        # pass traffic to this address

## now do the ipv4 address:
  set -- $( ip route get to 1.2.3.4)
  dest=$1 ; shift
  if test "_$dest" = "_unreachable" ; then
    return 1
  fi
  while test -n "$*" ; do
    key=$1 ; shift
    case $key in
      dev)
        dev4=$1 ; shift
      ;;
      src)
        src4=$1 ; shift
        ### 1>&2 echo "<<<<<<<<< src4 '$src4'"
      ;;
      *)
        junk=$1 ; shift
      ;;
    esac
  done || true

  echo $src6
  echo $src4
  echo $dev6
} ## end function

# Extract from routing tables and print:
# 1) the IPv6 address that is (probably?)
#  the address we should register with DNS, followed by
# 2) the Ipv4 address, followed by
# 3) the device name for ipv6 traffic (e.g. eth0).
#
# Or ... if we can't find a good IPv6 address;
# print nothing, and return with error status=1.
function get_our_ipaddrs__overcomplicated() {
  local dest key dev6 src6 src4 junk
  local geturl
  geturl=$( geturl ) || exit 2

# some random meaningless IP address here:
  array=( $( ip -6 route get 1:: ) )
  while (( ${#array[@]} > 0 )) ; do
    verb="${array[0]}" ;  array=( "${array[@]:1}" )
    if test _$verb = _dev ; then
      dev6="${array[0]}"
      break;
    fi
  done

# scan for linklocal address
  while read xxx && array=( $xxx )  ; do
    scope=''
    linklocal=''
    while (( ${#array[@]} > 0 )) ; do
      verb="${array[0]}" ;  array=( "${array[@]:1}" )
      if test _$verb = _inet6 ; then
        linklocal="${array[0]}" ;  array=( "${array[@]:1}" )
      elif test _$verb = _scopeid ; then
        scope="${array[0]}" ;  array=( "${array[@]:1}" )
##        1>&2 echo got scope $scope
      fi
    done # loop over all words on line
    ### 1>&2 echo ">>>> end of line: $linklocal $scope ${scope%<link>}"
    if test -n "$scope" -a _"${scope%<link>}" != _"${scope}" ; then
      break
    fi
  done < <(ifconfig $dev6)     # loop over all lines


  ## 1>&2 echo ">>>> local: $linklocal"

  array=( $( sed 's/:/ /g' < <(echo $linklocal)) )

  ### 1>&2 echo ">>>> array: ${array[@]}"
  target="${array[-2]}:${array[-1]}"

  while read xxx && array=( $xxx )  ; do
    routable=''
    scope=''
    while (( ${#array[@]} > 0 )) ; do
      verb="${array[0]}" ;  array=( "${array[@]:1}" )
      if test _$verb = _inet6 ; then
        routable="${array[0]}" ;  array=( "${array[@]:1}" )
      elif test _$verb = _scopeid ; then
        scope="${array[0]}" ;  array=( "${array[@]:1}" )
      fi
    done # loop over all words on line
    ### 1>&2 echo ">>>> end of line: $routable"
    if test -n "$routable" -a _"${routable%$target}" != _"${routable}" ; then
      if test -n "$scope" -a _"${scope%<global>}" != _"${scope}" ; then
        ### 1>&2 echo ">>>> bingo $routable $scope"
        break
      fi
    fi
  done < <(ifconfig $dev6)     # loop over all lines

  src6="$routable"        # pass traffic to our routable address
  ### 1>&2 echo "<<<<<<<<< routable '$routable'"

## now do the ipv4 address:
  set -- $( ip route get to 1.2.3.4)
  dest=$1 ; shift
  if test "_$dest" = "_unreachable" ; then
    return 1
  fi
  while test -n "$*" ; do
    key=$1 ; shift
    case $key in
      dev)
        dev4=$1 ; shift
      ;;
      src)
        src4=$1 ; shift
        ### 1>&2 echo "<<<<<<<<< src4 '$src4'"
      ;;
      *)
        junk=$1 ; shift
      ;;
    esac
  done || true

  echo $src6
  echo $src4
  echo $dev6
} ## end function

function get_our_ipaddrs__old() {
  local dest key dev6 src6 src4 junk
  local geturl
  geturl=$( geturl ) || exit 2

# random meaningless destination ip here:
  set -- $( ip route get to 2002:0aff:ffff::0 )
  dest=$1 ; shift
  if test "_$dest" = "_unreachable" ; then
    return 1
  fi
  while test -n "$*" ; do
    key=$1 ; shift
    case $key in
      dev)
        dev6=$1 ; shift
      ;;
      src)
        outside=$1; shift
      ;;
      *)
        junk=$1 ; shift
      ;;
    esac
  done || true

# replace src6 with something better:

  :; read junk1 inside junk2 < <(
     ifconfig $dev6 | grep inet6 | grep -v fe80 | grep -v $outside
   )

  src6="$inside"        # pass traffic to our inside address
  1>&2 echo "<<<<<<<<< outside '$outside'"
  1>&2 echo "<<<<<<<<< src6 '$src6'"

## now do the ipv4 address:
  set -- $( ip route get to 1.2.3.4)
  dest=$1 ; shift
  if test "_$dest" = "_unreachable" ; then
    return 1
  fi
  while test -n "$*" ; do
    key=$1 ; shift
    case $key in
      dev)
        dev4=$1 ; shift
      ;;
      src)
        src4=$1 ; shift
        ### 1>&2 echo "<<<<<<<<< src4 '$src4'"
      ;;
      *)
        junk=$1 ; shift
      ;;
    esac
  done || true

  echo $src6
  echo $src4
  echo $dev6
} ## end function


# Typical usage:
# pid=$( running_pid /var/run/radvd.pid )
function running_pid() {
  pidfile="$1"
  if ! test -r $pidfile ; then
    ### 1>&2 echo "Cannot read pidfile '$pidfile'"   ## rather common
    return              # no pidfile
  fi
  pid=$( cat $pidfile )
  if test -z "$pid" ; then
    1>&2 echo "????pidfile without pid: '$pidfile'"
    return ''           # empty pidfile?  Should never happen
  else
    if ! kill -0 $pid 2> /dev/null ; then
      # 1>&2 echo "Pidfile '$pidfile' exists, but pid $pid is not running."
      rm $pidfile
      return
    else
      # 1>&2 echo "Pid $pid is running"
      echo $pid
    fi
  fi
}

have_ip_conf_utils=yes
