#!/bin/sh
export CHESHIRE=/usr/local/cheshire

##
# Grin
#
# To be called from inetd, or as a standalone daemon using faucet.  It acts
# like an http server, handing back a redirect or splash page, and calling
# firewall rules if the user POSTs any data (e.g. they accepted an AUP).
#
# Be sure to set CHESHIRE above to wherever you installed the package.
#

export PATH=$CHESHIRE/bin:/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin

##
# Import the configuration here.  One good optimization would be to do this
# once, and propagate the environment to a child that does the work (rather
# than eval on every hit.)  If you bootstrap yourself, comment this out for
# greater speed.
#
if [ -z "$HostName" -o "$DEBUG" ]; then
  eval `$CHESHIRE/bin/bootstrap $CHESHIRE/cheshire.conf`
fi

##
# fix-ups to ease syntax without breaking nocat firewall rules
#
export InternalDevice=$SplashDevice
export LocalNetwork=$SplashNetwork
export ExternalDevice=$GatewayDevice

##
# Initialize Logging
#
if [ "$LogFacility" == "syslog" ]; then
  LOG="logger -p $SyslogFacility.$SyslogPriority -t $SyslogIdent"
  LOGPOST=""
else 
  LOG="echo `date`"
  LOGPOST="1>&2" # print to STDERR
fi

##
# Call with -R to reset the firewall and exit
#
if [ "$1" == "-R" ]; then
  echo "Resetting firewall..."

  if [ ! -x $CHESHIRE/libexec/initialize.fw ]; then
    echo "Problem: can't execute $CHESHIRE/libexec/initialize.fw"
    echo "Check that CHESHIRE is properly defined in $0."
    exit
  fi
  $CHESHIRE/libexec/initialize.fw  

  echo "Clearing MAC cache..."
  rm -f $DataDir/*

  if [ "$Owners" ]; then
    echo "AutoLogin: Owners..."
    for entry in $Owners; do
      eval $LOG AutoLogin: Owner: $entry $LOGPOST
      if [ `echo $entry | grep ':../'` ]; then  # Must be a MAC/IP pair
        MAC=`echo $entry | cut -f 1 -d '/'`
        IP=`echo $entry | cut -f 2 -d '/'`
        $CHESHIRE/libexec/access.fw permit $MAC $IP Owner
      else
        $CHESHIRE/libexec/ipaccess.fw permit $entry Owner
      fi
    done
  fi
  if [ "$Members" ]; then
    echo "AutoLogin: Members..."
    for entry in $Members; do
      eval $LOG AutoLogin: Member: $entry $LOGPOST
      if [ `echo $entry | grep ':../'` ]; then  # Must be a MAC/IP pair
        MAC=`echo $entry | cut -f 1 -d '/'`
        IP=`echo $entry | cut -f 2 -d '/'`
        $CHESHIRE/libexec/access.fw permit $MAC $IP Member
      else
        $CHESHIRE/libexec/ipaccess.fw permit $entry Member
      fi
    done
  fi
  if [ "$Banned" ]; then
    echo "Banning bad guys..."
    for entry in $Banned; do
      eval $LOG Banned: $entry $LOGPOST
      if [ `echo $entry | grep ':../'` ]; then  # Must be a MAC/IP pair
        MAC=`echo $entry | cut -f 1 -d '/'`
        IP=`echo $entry | cut -f 2 -d '/'`
        $CHESHIRE/libexec/ban.fw $MAC $IP
      else
        $CHESHIRE/libexec/ban.fw $entry
      fi
    done
  fi
  if [ "$AllowedNetworks" ]; then
    echo "Adding AllowedNetworks..."
    for entry in $AllowedNetworks; do
      eval $LOG AutoLogin: Network: $entry $LOGPOST
      $CHESHIRE/libexec/destination.fw $entry Member
    done
  fi

  exit
fi

##
# Save a CR and some other variables for later
#
CR=`printf '\015'`
VERSION="Cheshire/0.02"
DATE=`date -u +'%a, %d %b %Y %X GMT'`

##
# For all eternity...
#
while : ; do 
  ##
  # Read a line in at a time, saving the first 3 fields and stopping at 1k
  #
  read -n 1024 a b c;

  ##
  # Is it a GET or POST (or other METHOD)?
  #
  if [ `echo $a | grep '^[A-Z]*$'` ]; then
    ##
    # Save the method and URL, stripping CR
    #
    export METHOD=$a
    export URL=`echo $b | tr -d '\r'`

  #
  # If it's not a METHOD, then is it the Host: line?
  #
  elif [ "$a" == "Host:" ]; then
    export HOST=`echo $b | tr -d '\r'`
  fi

  if [ "$URL" -a "$HOST" ]; then	# Do we have both?
    break				# Then we're done.
  fi

  if [ -z "$a" -o "$a" == "\r" ]; then	# Hit a blank line?
    break				# Then bail.
  fi
done

if [ -z "$HostName" ]; then		# uh-oh.  No config file?
					# Notify them in HTML.
  cat <<EOERR
HTTP/1.1 200 OK$CR
Date: $DATE$CR
Server: $VERSION$CR
Last-Modified: $DATE$CR
Content-Type: text/html$CR
$CR
<html><body>
<h1>Cheshire: Configuration problem</h1>
I can't seem to find <i>HostName</i>.  Is it defined in <b>cheshire.conf</b>?
</body></html>
EOERR

  eval $LOG Configuration problem: HostName undefined $LOGPOST
  exec awk '{system("")}' < /dev/null
fi

# Get the IP using getpeername, from netpipes
IP=`getpeername | tail -1`

# ...and the MAC from the arp cache
#MAC=`arp -an|grep $IP |sed -n -e 's/.*\(..:..:..:..:..:..\).*/\1/p'`
MAC=`grep $IP /proc/net/arp |awk '{print $4}'`

##
# If the HOST matches our hostname, then stop redirecting, and hand them
# the template or let them through.
#
if [ "`echo $HOST | sed -e 's/:.*//'`" == "$HostName" ]; then

  ##
  # If this is a GET or HEAD, then serve it.
  #
  if [ "$METHOD" == "GET" ]; then

    TEMP="$TempDir/tmp.$$"

    ##
    # Strip the query string
    #
    URL=`echo $URL | sed -e 's/?.*//'`
                                     
    ##
    # attempt to escape nasties here
    #
    URL=`echo $URL | sed -e 's/[^a-zA-Z0-9_\-\/\\\.,\?]//g' -e 's/\.\.//g'`   

    #
    # If they're requesting nothing in particular, give 'em the form
    #
    if [ "$URL" == "/" ]; then
      URL=$SplashForm
    fi

    if [ "$URL" == "/status" ]; then
      ##
      # Log the request
      #
      eval $LOG http: $IP $MAC requests status $LOGPOST

      echo "cat <<EOF" > $TEMP
      echo "HTTP/1.1 200 OK$CR" >> $TEMP
      echo "Date: $DATE$CR" >> $TEMP
      echo "Server: $VERSION$CR" >> $TEMP
      echo "Last-Modified: $DATE$CR" >> $TEMP
      echo "Content-Type: text/html$CR" >> $TEMP
      echo "$CR" >> $TEMP

      ##
      # If it's a HEAD, don't bother with content
      #
      if [ "$METHOD" == "GET" ]; then
        echo '<html><head><title>Gateway Status for $GatewayName</title></head><body>' >> $TEMP
        echo '<h2>Node $GatewayName ($HostName)</h2><p>Current status:</p>' >> $TEMP

        ##
        # TOTAL users == the number of files in DataDir
        #
	TOTAL=`ls $DataDir | wc -l |awk '{print $1}'`
        if [ "$TOTAL" == "0" ]; then
          echo "No users connected." >> $TEMP
        else 
          echo "<table border=1><tr><th valign=bottom>User</th><th>Connected<br>(seconds)</th><th>Time left<br>(seconds)</th><th valign=bottom>MAC Address</th></tr>" >> $TEMP

          for FILE in $DataDir/*; do
            DATA=`cat $FILE`
            DELTA=$((`date +'%s'` - `awk '{print $3}' < $FILE`))
            LEFT=$(($LoginTimeout - $DELTA))
            IP=`echo $DATA | awk '{print $1}'`
            MAC=`echo $DATA | awk '{print $2}' | sed 's/..:..:..$/XX:XX:XX/'`
            OUI=`echo $MAC | cut -c 1,2,4,5,7,8`
            echo "<tr><td align='left'>$IP</td><td align='center'>$DELTA</td><td align='center'>$LEFT</td><td align='left'><a href="http://standards.ieee.org/cgi-bin/ouisearch?$OUI">$MAC</a></td></tr>" >> $TEMP
          done

          echo "</table><p>Total users: <b>$TOTAL</b></p>" >> $TEMP
        fi
        echo "</body></html>" >> $TEMP
      fi
      
      echo 'EOF' >> $TEMP
      ##
      # Execute it(!) to do variable interpolation in the here document, and
      # automatically cat it to STDOUT.
      #
      sh $TEMP
      rm -f $TEMP
      exec awk '{system("")}' < /dev/null
    fi
    
    if [ -f $DocumentRoot/$URL ]; then
      ##
      # grab the file extension for proper mime handling
      #
      FILETYPE=`echo $URL | sed -e 's/.*\.\([a-z]*\)$/\1/g'`
      if [ -z "$FILETYPE" ]; then
        FILETYPE="txt"
      fi
 
      ##
      # Lame mime type support.
      #     
      case "$FILETYPE" in
      "html"|"htm")
        MIMETYPE="text/html"
        ;;
      "txt")
        MIMETYPE="text/plain"
        ;;
      "gif"|"jpeg"|"png")
        MIMETYPE="image/$FILETYPE"
        ;;
      "jpg")
        MIMETYPE="image/jpeg"
        ;;
      *)
        MIMETYPE="application/octet-stream"
        ;;
      esac

      ##
      # html gets variable interpolation
      #      
      if [ "$MIMETYPE" == "text/html" ]; then
        echo 'cat <<EOF' > $TEMP 
        cat $DocumentRoot/$URL >> $TEMP 
        echo '' >> $TEMP
        echo 'EOF' >> $TEMP
        sh $TEMP > $TEMP.x

        ##
        # We do interpolation first, then wc it for the content-length
        #
        LENGTH=`wc -c $TEMP.x | awk '{print $1}'`

        cat <<EOF
HTTP/1.1 200 OK$CR
Date: $DATE$CR
Server: $VERSION$CR
Last-Modified: $DATE$CR
Content-Length: $LENGTH$CR
Content-Type: text/html$CR
$CR
EOF
        ##
        # If it's a HEAD, don't bother with content
        #
        if [ "$METHOD" == "GET" ]; then
          cat $TEMP.x
        fi

        rm -f $TEMP
        rm -f $TEMP.x
        exec awk '{system("")}' < /dev/null
      ##
      # Everything else simply gets a Content-Length and the contents
      #
      else
        LENGTH=`wc -c $DocumentRoot/$URL | awk '{print $1}'`
        cat <<EOMIME
HTTP/1.1 200 OK$CR
Date: $DATE$CR
Server: $VERSION$CR
Last-Modified: $DATE$CR
Content-Length: $LENGTH$CR
Content-Type: $MIMETYPE$CR
$CR
EOMIME
        ##
        # If it's a HEAD, don't bother with content
        #
        if [ "$METHOD" == "GET" ]; then
	  cat $DocumentRoot/$URL
	fi
	
        exec awk '{system("")}' < /dev/null
      fi

    #
    # If we're here, we have no idea what they're asking for.
    #
    else 
      cat <<EOF
HTTP/1.1 404 Not Found$CR
Date: $DATE$CR
Server: $VERSION$CR
Last-Modified: $DATE$CR
Content-Type: text/html$CR
$CR
<html><body>
<h1>Document not found.</h1>
Can't find $URL.
</body></html>
EOF
      exec awk '{system("")}' < /dev/null
    fi


  ##
  # If this is a POST, then they've hit 'Login'
  #
  elif [ "$METHOD" == "POST" ]; then

    #
    # Sanity check here:  what to do if no MAC?  IP only policy?
    #
    
    # Now we'll save IP MAC EPOC_SECONDS in data/MAC for later 
    DATE=`date +'%s'`
    echo "$IP $MAC $DATE" > $CHESHIRE/data/$$

    # strip the redirect line from the URL
    URL=`echo $URL |sed -e 's/.*redirect=\(.*\)/\1/'`
    if [ -z "$URL" ]; then
      URL=$HomePage
    fi

    # finally, let them in
    eval $LOG Login: $MAC $IP $LOGPOST
    $CHESHIRE/libexec/access.fw permit $MAC $IP Public

    # ...and hand them back to where they wanted to go.    
    cat <<EOF
HTTP/1.1 302 Moved$CR
Date: $DATE$CR
Server: $VERSION$CR
Location: http://$URL$CR
$CR
<html>
<body bgcolor="white" text="black">
You should be redirected now.  
If not, click <a href="http://$URL">here.</a>
</body>
</html>
EOF

    exec awk '{system("")}' < /dev/null

  ##
  # Ignore all other requests
  #
  else
    cat <<EOERR
HTTP/1.1 405 Method Not Allowed$CR
Date: $DATE$CR
Server: $VERSION$CR
Allow: GET, HEAD, POST
Content-Type: text/html$CR
$CR
<html><body>
<h1>$METHOD method is unsupported</h1>
</body></html>
EOERR
 
   exec awk '{system("")}' < /dev/null

  fi
fi

##
# If we're here, the requested host didn't match HostName, so redirect them
# to $HostName/?redirect=$HOST$URL.
#
cat <<EOF
HTTP/1.1 302 Moved$CR
Date: $DATE$CR
Server: $VERSION$CR
Location: http://$HostName:$GatewayPort/?redirect=$HOST$URL$CR
$CR
<html>
<body>
You should be redirected now.  
If not, click <a href="http://$HostName:$GatewayPort/?redirect=$HOST$URL">here.</a>
</body>
</html>
EOF

eval $LOG Capturing: $MAC $IP $LOGPOST
exec awk '{system("")}' < /dev/null

#
# Ende
#
