#!/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, without additional options ## ### Original netcat ##NETCAT="nc.traditional" ## ### ncat (from nmap) ##NETCAT="ncat" ## ### OpenBSD netcat NETCAT="nc" ## ## NETCAT_OPTS: any additional options to netcat ## NETCAT_OPTS="-v" ## ## 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 } is_openbsd=$(echo "${NETCAT}" | grep -i "certfile") 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" -a -n "${is_openbsd}" ] && { echo "Genuine OpenBSD nc detected" >&2 mkfifo -m 600 "$INF" # shellcheck disable=SC2050 while :; do # shellcheck disable=SC2094 ${GOSHER_SERVE} "${GOPHERDIR}" \ "${HOSTNAME}" "${PORT}" <"$INF" |\ ${NETCAT} ${NETCAT_OPTS} -l "${HOSTNAME}" "${PORT}" >"$INF" sleep 1 done rm -f $INF exit 0 } [ "$STYLE" = "pipe" -a -z "${is_openbsd}" ] && { echo "Other nc implementation detected" >&2 mkfifo -m 600 "$INF" # shellcheck disable=SC2050 while :; do # shellcheck disable=SC2094 ${GOSHER_SERVE} "${GOPHERDIR}" \ "${HOSTNAME}" "${PORT}" <"$INF" |\ ${NETCAT} ${NETCAT_OPTS} -l -p "${PORT}" >"$INF" sleep 1 done rm -f $INF exit 0 } [ "$STYLE" = 'fork' ] && { # shellcheck disable=SC2050 while :; do ${NETCAT} ${NETCAT_OPTS} -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/ / /g;s/server\|port\]$/$HOSTNAME $PORT/") line=$(echo "$line" | sed -r -e 's/^\[//;s/\]$//;s/\|/ /g;s/ //1') ;; 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