#!/bin/sh
# 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 3 of the License, 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, see .
#
# ----------------------------------------------------------------------
##
## === gosher ===
##
## gosher is a simple Gopher server in a POSIX shell script:
##
## $ ./gosher [ [ []]]
##
## If GOPHERDIR is not specified, "./" is assumed.
## If HOSTNAME is not specified, "localhost" is used.
## If PORT is not specified, the default port "70" is used.
##
## (c) 2018 Vincenzo 'KatolaZ' Nicosia
##
##
######################
##
## If the script is called with basename "gosher", launch the netcat
## server...
##
##
## NETCAT: the netcat command to use, and any additional option
##
### Original netcat
##NETCAT="nc.traditional"
##
### ncat (from nmap)
##NETCAT="ncat"
##
### OpenBSD netcat
NETCAT="nc"
##
## STYLE: The way in which netcat will talk to gosher_serve
##
### fork with "-c" (Does *not* work with OpenBSD netcat!!!!!)
#STYLE='fork'
##
### use named pipes (Does *not* work with original netcat!!!!!)
STYLE='pipe'
### prefix of the input FIFO
IPREFIX=/tmp/inf_
DEBUG=
#DEBUG=yes
[ -n "$DEBUG" ] && {
set -e -x
}
## function
cleanup(){
[ -n "$INF" ] && [ -p "$INF" ] && rm -f "${INF}"
exit 1
}
MYNAME=$(basename "$0")
MYDIR=$(dirname "$(readlink -f "$0")")
NETCAT=$(which "$NETCAT")
if [ -z "${MYNAME#gosher}" ]; then
## we are called as gosher -- launch the server
GOPHERDIR=${1:-"./"}
HOSTNAME=${2:-"localhost"}
PORT=${3:-70}
[ ! -f "${NETCAT}" ] || [ ! -x "${NETCAT}" ] && {
echo "Wrong NETCAT -- Exiting" >&2
exit 2
}
if [ -f "${MYDIR}/gosher_serve" ] || [ -h "${MYDIR}/gosher_serve" ]; then
GOSHER_SERVE="${MYDIR}/gosher_serve"
trap cleanup 0 HUP INT TRAP TERM QUIT
INF="${IPREFIX}$$"
[ "$STYLE" = "pipe" ] && {
mkfifo -m 600 "$INF"
# shellcheck disable=SC2050
while :; do
# shellcheck disable=SC2094
${GOSHER_SERVE} "${GOPHERDIR}" \
"${HOSTNAME}" "${PORT}" <"$INF" |\
${NETCAT} -vvvvv -l "${HOSTNAME}" "${PORT}" >"$INF"
sleep 1
done
rm -f $INF
exit 0
}
[ "$STYLE" = 'fork' ] && {
# shellcheck disable=SC2050
while :; do
${NETCAT} -vv -l -p "$PORT" -c \
"${GOSHER_SERVE} ${GOPHERDIR} ${HOSTNAME} ${PORT}"
sleep 1
done
exit 0
}
echo "Wrong STYLE specified -- Exiting" >&2
exit 2
else
echo "Cannot find gosher_serve -- Exiting">&2
exit 3
fi
fi
######################
##
## ...otherwise, serve a request
##
## function
invalid_selector(){
sel="$1"
echo "[$(date +%Y-%m-%d\ %H:%M:%S)|ERROR|${sel}|\"\"]" >&2
printf "3Error: Invalid selector\tErr\t\t\r\n.\r\n"
# shellcheck disable=SC1117
exec 1>&-
exec 2>&-
exit 1
}
## function
serve_selector(){
sel="$1"
cat "${sel}"
echo "[$(date +%Y-%m-%d\ %H:%M:%S)|SELECTOR|${sel}|\"\"]" >&2
sleep 1
exec 1>&-
exec 2>&-
exit 0
}
### transform a .gph file into a gophermap
## function
serve_index(){
IDX=$1
echo "[$(date +%Y-%m-%d\ %H:%M:%S)|GPH|${IDX}|\"\"]" >&2
while read -r line; do
rline=$(echo "$line" | tr -d '\r')
case "$rline" in
'['*)
line=$(echo "$rline" | sed -r -e 's/^\[//;s/\]$//;s/\|/ /g;s/ / /;')
line=$(echo "$line" | sed -r -e "s/server port/$HOSTNAME $PORT/")
;;
t*)
line=$(echo "$rline" | cut -b 2-)
;;
*)
;;
esac
printf '%s\r\n' "$line"
done < "$IDX"
# shellcheck disable=SC1117
printf ".\r\n"
exec 1>&-
exec 2>&-
exit 0
}
### Serve an HTML URL through a redirect page
## function
serve_redirect(){
url=$1
echo "[$(date +%Y-%m-%d\ %H:%M:%S)|REDIRECT|${url}|\"\"]" >&2
cat<< EOF
gopher redirect
Click to be redirected to: $url
EOF
exec 1>&-
exec 2>&-
exit 0
}
### Serve a CGI -- Set the environment and run the corresponding script
## function
serve_cgi(){
script_name=$( echo "$1" | sed -r 's:^/+::')
query_string="$2"
echo "[$(date +%Y-%m-%d\ %H:%M:%S)|CGI|${script_name}|\"${query_string}\"]" >&2
GATEWAY_INTERFACE="CGI/1.1"
PATH_INFO="${script_name}"
PATH_TRANSLATED="${script_name}"
[ -n "${QUERY_STRING}" ] && QUERY_STRING="${query_string}"
REMOTE_ADDR=
REMOTE_HOST=
REQUEST_METHOD="GET"
SCRIPT_NAME="${script_name}"
SERVER_NAME="${HOSTNAME}"
SERVER_PORT="${PORT}"
SERVER_PROTOCOL="gopher/1.0"
SERVER_SOFTWARE="gosher"
####X_GOPHER_SEARCH= search (See above.)
export GATEWAY_INTERFACE PATH_INFO PATH_TRANSLATED QUERY_STRING
export REMOTE_ADDR REMOTE_HOST REQUEST_METHOD SCRIPT_NAME
export SERVER_NAME SERVER_PORT SERVER_PROTOCOL SERVER_SOFTWARE
${GOPHERDIR}${script_name} "" "${query_string}" "${HOSTNAME}" "${PORT}" 2>&1
exec 1>&-
exec 2>&-
exit $?
}
GOPHERDIR=${1:-"./"}
HOSTNAME=${2:-"localhost"}
PORT=${3:-"70"}
read -r selector
selector=$(echo "$selector" | tr -d '\r' )
[ -n "$DEBUG" ] && {
echo "iGOPHERDIR: ${GOPHERDIR}"
echo "iselector: \"${selector}\""
}
case "$selector" in
URL:*)
## it's a special URL selector
url=$(echo "$selector" | cut -d ":" -f 2-)
serve_redirect "$url"
;;
/?*.cgi*)
## it's a CGI
script_name=$(echo "$selector" | cut -d "?" -f 1)
query_string=$(echo "$selector" | cut -d "?" -f 2)
[ "${script_name}" = "${query_string}" ] && query_string=""
RP1=$(readlink -f "${GOPHERDIR}""${script_name}")
# shellcheck disable=SC2181
[ $? -eq 0 ] || invalid_selector "${selector}"
RP2=$(readlink -f "${GOPHERDIR}")"${script_name}"
RP2=$(echo "${RP2}" | sed -r 's:/+:/:g')
# shellcheck disable=SC2181
[ $? -eq 0 ] || invalid_selector "${selector}"
[ -n "$DEBUG" ] && {
echo "iRP1: ${RP1}"
echo "iRP2: ${RP2}"
}
[ "${RP1}" = "${RP2}" ] && {
[ -x "${RP1}" ] && {
serve_cgi "${script_name}" "${query_string}"
}
}
invalid_selector "${selector}"
;;
/?*|"")
## it's a regular selector
RP1=$(readlink -f "${GOPHERDIR}"/"${selector}")
# shellcheck disable=SC2181
[ $? -eq 0 ] || invalid_selector "$selector"
RP2=$(readlink -f "${GOPHERDIR}")"${selector}"
# shellcheck disable=SC2181
[ $? -eq 0 ] || invalid_selector "$selector"
[ -n "$DEBUG" ] && {
echo "iRP1: ${RP1}"
echo "iRP2: ${RP2}"
}
if [ "${RP1}" = "${RP2}" ]; then
if [ -f "${RP1}" ]; then
if [ -n "$(echo "${RP1}" | sed -n '/\.gph$/p')" ]; then
serve_index "${RP1}"
else
serve_selector "${RP1}"
fi
elif [ -d "${RP1}" ]; then
[ -f "${RP1}/gophermap" ] && serve_selector "${RP1}/gophermap"
[ -f "${RP1}/index.gph" ] && serve_index "${RP1}/index.gph"
fi
fi
invalid_selector "$selector"
;;
*)
## we don't know what it is -- try to default to a
## gophermap
[ -f "${GOPHERDIR}/gophermap" ] && serve_selector "${GOPHERDIR}/gophermap"
[ -f "${GOPHERDIR}/index.gph" ] && serve_index "${GOPHERDIR}/index.gph"
echo "got invalid selector: \"$selector\"" >&2
invalid_selector "/"
;;
esac