#! /bin/ash #####Configuration section##### #The fqdn variable defines the hostname of the gopher server you'll be proxying to gemini. readonly fqdn=gopher.zcrayfish.soy #The port variable defines the TCP port of the gopher server you'll be proxying to gemini. readonly port=70 #The full path to the gophermap2gemini.awk script readonly gophermap2gemini=/usr/local/bin/gophermap2gemini.awk #Use curl, or use the gopher daemon directly readonly usecurl=false #full path to gopher daemon readonly gopherd=/usr/sbin/gophernicus #command options to pass to the gopher daemon readonly gopherd_options="-h $fqdn -nv -nf -np -f /srv/gopher/filters -e pdf=P -e webp=I -e eml=m -e uue=6 -o utf-8 -w 74" ####End of configuration section, use caution if editing below this line#### ### Export some environs to anything we run, could be useful export REMOTE_HOST export REMOTE_PORT export REMOTE_ADDR="$REMOTE_HOST" ### readonly baseurl="gemini://$fqdn" #how many bytes are at the beginning of the URL? readonly baseurllength="${#baseurl}" #readonly baseurllength=$((${#baseurl}-1)) # Gather request read -t 30 -r url badclient ####Request validation#### #Did we time out... If so just exit, gemini does not have a timeout code test "$?" != "0" && exit # Reject requests with garbage past the URL. test ! -z "$badclient" && printf '%s\15\12' "59 BAD REQUEST; Garbage past URL in request." && exit # See if request is for defined FQDN. test "$(echo "$url" | head -c "$baseurllength")" != "$baseurl" && printf '%s\15\12' "59 BAD REQUEST; $baseurl URLs only please." && exit ####End of request validation#### # If it all looks good, find out what they want filename=$(echo "$url" | tail -c +$((${#baseurl}+1)) | sed -e 's/%2c/,/gi' -e 's/%20/ /g' -e 's/\r$//g' -e 's/%3b/;/i') # ^ ^ ^ ^ ^ ^ # curl fails without carriage return removal!!!! filename2=$(echo "$filename" | tail -c +3) readonly filename # Extract query string, if present, and export it in case if non-gophernicus gopherd QUERY_STRING="$(echo "$url" | grep "?" | cut -d? -f2-)" export QUERY_STRING export SEARCHREQUEST="$QUERY_STRING" ###WIP BELOW HERE#### case "$filename" in /favicon.ico) printf '%s\15\12' "51 NOT FOUND; favicon.ico" && exit;; /2*) printf '%s\15\12' "50 PERMANENT FAILURE; gophertype 2 CCSO not supported" && exit;; /7*) if [ -z "$QUERY_STRING" ] then printf '%s\15\12' "10 This is a searchable gopher index. Enter search keywords" else mimetype="20 text/gemini; " if [ "$usecurl" = "true" ] ; then xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | awk -f $gophermap2gemini | \ sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )" else # shellcheck disable=2086 xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | awk -f $gophermap2gemini | \ sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )" fi fi ;; ###START OF DUMB / NON-INTELLIGENT GOPHER TYPES### /[04569IMPdghps]*) case "$filename" in /0/stylesheet.css) mimetype="20 text/css";; /0*) mimetype="20 text/plain";; /4*) mimetype="20 application/mac-binhex40";; /5*|/6*|/9*|/d*) mimetype="20 application/octet-stream";; /I*.webp) mimetype="20 image/webp";; /I*) mimetype="20 image/jpeg";; /M*.mht|/M*.mhtml|/m*.mht|m*.mhtml) mimetype="20 multipart/related";; /M*) mimetype="20 message/rfc822";; /P*) mimetype="20 application/pdf";; /g*) mimetype="20 image/gif";; /h*|/H*) mimetype="20 text/html";; /p*) mimetype="20 image/png";; /s*.mp3) mimetype="20 audio/mpeg";; /s*.m4a) mimetype="20 video/mp4";; /s*) mimetype="20 application/octet-stream";; /';'*.webm) mimetype="20 video/webm";; esac #We now have enough information to pull in anything we're not converting to gemini markup if [ "$usecurl" = "true" ] ; then xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | base64)" else # shellcheck disable=2086 xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | base64)" fi isdumb="true" ;; ###END OF DUMB / NON-INTELLIGENT GOPHER TYPES### *) #Convert gophermap to gemini markup mimetype="20 text/gemini; " if [ "$usecurl" = "true" ] ; then xyzzy="$(curl -q --disable -s --output - "gopher://$fqdn:$port$filename" | awk -f $gophermap2gemini | \ sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )" else # shellcheck disable=2086 xyzzy="$(echo "$filename2" | ${gopherd} ${gopherd_options} | awk -f $gophermap2gemini | \ sed -e 's/=> gopher:\/\/'$fqdn':'$port'/=> gemini:\/\/'$fqdn'/g' )" fi ;; esac ###OUTPUT SECTION### #We'll use the output of curl to determine if gopher content is 404d #Non gophermap content may be binary, base64 it so the shell can deal with it if [ "$isdumb" = "true" ] ; then precontent=$(echo "$xyzzy" | base64 -d) else precontent="$xyzzy" fi #See if there's an error is40=$(echo "$precontent" | head -n1 | grep "Error: Access denied!") is51=$(echo "$precontent" | head -n1 | grep "Error: File or directory not found!") #Respond to any errors if [ -n "$is40" ] ; then printf '%s\15\12' '40 TEMPORARY FAILURE; Error: Access denied!' && exit fi if [ -n "$is51" ] ; then printf '%s\15\12' '51 NOT FOUND; Error: File or directory not found!' && exit fi #If no errors, then we go here printf '%s\15\12' "$mimetype" if [ "$isdumb" = "true" ] ; then echo "$xyzzy" | base64 -d else echo "$xyzzy" fi