Thursday, September 10, 2009

Basic Authenticating pseudo-proxy with bash/netcat

Yes, you can also do authentication with netcat.


#!/bin/bash
# @author : Prashant Borole (prashantb  :can be reached at:     cse iitb ac in)

# A simple proxy-proxy that authenticates on users' behalf.
# Known limitations :
#       Works for Basic proxy authentication at the moment (though it is not that hard to add digest support)
#       Can process single request at a time. Any parallel requests will not be satisfied. This may lead to referred elements not being loaded, eg. images. Download accelerators are rendered useless.
#       Due to the logging format, there can be storage issues when downloading huge files.
#       Significantly slower than squid or some custom asyncio implementation as each request spawns bunch of new processes
#       No cache. Each request is strictly sent to upstream proxy
#       no https or ftp support

# TODO put this in some standard location
CONFIG_FILE="$HOME/.proxy.conf"

export LISTEN_PORT=
export PROXY=
export PROXY_PORT=
export LOG_FILE=

init()
{
        exec   2>&- 2>&1
        exec   5>&1
        exec   >>"$LOG_FILE"
}

readConfig()
{
        # really cheap config parsing.
        # FIXME improve
        # echo "Loading Configuration from $CONFIG_FILE"
        export LISTEN_PORT=$(grep "LISTEN_PORT=" "$CONFIG_FILE" | cut -d '=' -f2)
        export PROXY=$(grep "PROXY=" "$CONFIG_FILE" | cut -d '=' -f2)
        export PROXY_PORT=$(grep "PROXY_PORT=" "$CONFIG_FILE" | cut -d '=' -f2)
        export LOG_FILE=$(grep "LOG_FILE=" "$CONFIG_FILE" | cut -d '=' -f2)
}

if [ "$1" != "--" ]; then
        # read the config initially
        readConfig
        
        # accept credentials
        echo -n "$PROXY:$PROXY_PORT    user name : "
        read USER_NAME
        echo -n "$USER_NAME@$PROXY:$PROXY_PORT Password : "
        read -s PASSWORD
        echo
        export B64CRED=`echo -ne "$USER_NAME:$PASSWORD" | base64`
        unset USER_NAME PASSWORD
        echo "Credentials accepted."

        # launch in background
        $0 -- &
        exit 0
else
        trap 'echo "cleaning up $TMP_DIR" ; rm -fr "$TMP_DIR"; killall -9  nc tee >/dev/null 2>&1' EXIT
        trap 'echo "Shutting down proxy server on port $LISTEN_PORT" ; exit 0' SIGUSR1
        trap 'echo "Reloading config..." ; readConfig' SIGUSR2
        trap 'echo "[UNCLEAN] Shutting down proxy server on port $LISTEN_PORT" ; exit 1' SIGINT SIGKILL SIGSEGV SIGPIPE SIGHUP SIGTERM SIGABRT

        readConfig
        init
        echo "`date`   Server $0[$$] started"
fi

TMP_DIR=`mktemp -d /tmp/proxy.XXXXXXXX`

BACK_FIFO="$TMP_DIR"/back_fifo
# create a pipe
mknod "$BACK_FIFO" p

echo "Accepting connections on port $LISTEN_PORT"
while true ; do
        nc -l "$LISTEN_PORT" 0<$BACK_FIFO | tee request | (while read line ; do if [ "$line" = "^M" ] ; then echo "Proxy-Authorization: Basic $B64CRED" ; echo ; else echo "$line" ; fi;  done) | nc -w 5 "$PROXY" "$PROXY_PORT" | tee response 1>$BACK_FIFO
        head -n+1 request
        head -n+1 response
done

echo "THIS SHOULD NEVER HAPPEN. SOMETHING IS WRONG !!!"
exit 1


Note that you can NOT use something like sed or awk, because they simply give up when there is no text to read. This breaks the pipe. A "while read" loop just cuts it :)