mirror of
https://github.com/sigmasternchen/ShellSpider
synced 2025-03-16 00:19:06 +00:00
345 lines
9.6 KiB
Bash
Executable file
345 lines
9.6 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
ssl="$2"
|
|
|
|
settingsfile="$1"
|
|
eval "$(cat $settingsfile)" # declare settings array
|
|
|
|
. misc.sh
|
|
|
|
declare -A server
|
|
server[remoteAddress]="$SOCAT_PEERADDR"
|
|
server[remotePort]="$SOCAT_PEERPORT"
|
|
server[remoteHost]="$(dig +noall +answer -x $SOCAT_PEERADDR | awk '{ print $5 }')"
|
|
server[serverAddress]="$SOCAT_SOCKADDR"
|
|
server[serverPort]="$SOCAT_SOCKPORT"
|
|
server[serverName]="${settings[name]}"
|
|
server[serverAdmin]="${settings[admin]}"
|
|
server[serverSoftware]="${settings[server]}"
|
|
server[documentRoot]="$(realpath "${settings[home]}")"
|
|
server[requestTime]="$(date +%s)"
|
|
server[requestTimeFloat]="$(($(date +%s%N)/1000))"
|
|
server[requestTimeReadable]="$(date --rfc-3339=ns)"
|
|
|
|
requestTimeout() {
|
|
echo "$(date --rfc-3339=ns) - ${server[remoteAddress]}:${server[remotePort]} - Error: request timeout" 1>&2
|
|
|
|
content="408 Request Timeout
|
|
The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time
|
|
"
|
|
status=408
|
|
|
|
length=$(printf "%s" "$content" | wc -c)
|
|
|
|
echo -en "HTTP/1.1 $status $(./statusString.sh $status)\r\n"
|
|
echo -en "Content-Length: $length\r\n"
|
|
echo -en "Content-Type: text/plain\r\n"
|
|
echo -en "\r\n"
|
|
printf "%s" "$content"
|
|
|
|
exit 1
|
|
}
|
|
|
|
trap requestTimeout SIGUSR1
|
|
|
|
headerTimeout=10
|
|
set +bm
|
|
headerTimer() {
|
|
pid=$1
|
|
timeout=$2
|
|
sleep $2
|
|
kill -USR1 $pid
|
|
}
|
|
headerTimer $$ $headerTimeout &
|
|
headerTimerPid=$!
|
|
|
|
declare -A headers
|
|
first=1
|
|
while true; do
|
|
IFS=$'\r' read header
|
|
if test "$header" = ""; then
|
|
break
|
|
fi
|
|
if test $first = 1; then
|
|
server[requestMethod]="$(echo "$header" | awk '{ print $1 }')"
|
|
server[http]="$(echo "$header" | awk '{ print $3 }' | awk -F/ '{ print $2} ')"
|
|
server[https]="$ssl"
|
|
server[serverProtocol]="$(echo "$header" | awk '{ print $3 }')"
|
|
server[request_unchecked]=$(echo "$header" | awk '{ print $2 }')
|
|
server[requestURI]="$(realpath -sm "${server[request_unchecked]}")"
|
|
server[queryString]="$(echo "${server[requestURI]}" | awk -F? '{for (i=2; i<=NF; i++) print $i}')"
|
|
server[scriptName]="$(echo "${server[requestURI]}" | awk -F? '{ print $1 }')"
|
|
server[scriptFilename]="$(realpath -sm "${settings[home]}${server[scriptName]}")"
|
|
first=0
|
|
continue
|
|
fi
|
|
headers[$(echo $header | awk -F: '{ print $1}')]="$(echo "$header" | awk '{for (i=2; i<=NF; i++) print $i}')"
|
|
done
|
|
|
|
kill -TERM $headerTimerPid
|
|
wait $headerTimerPid 2> /dev/null
|
|
|
|
if test "$first" = 1; then
|
|
# wut?
|
|
echo "$(date --rfc-3339=ns) - ${server[remoteAddress]}:${server[remotePort]} - Error: Malformed HTTP request; ignoring" 1>&2
|
|
exit 1
|
|
fi
|
|
|
|
server[httpAccept]="${headers[Accept]}"
|
|
server[httpAcceptCharset]="${headers[Accept-Charset]}"
|
|
server[httpAcceptEncoding]="${headers[Accept-Encoding]}"
|
|
server[httpAcceptLanguage]="${headers[Accept-Language]}"
|
|
server[httpConnection]="${headers[Accept-Connection]}"
|
|
server[httpHost]="${headers[Host]}"
|
|
server[httpReferer]="${headers[Referer]}"
|
|
server[httpUserAgent]="${headers[User-Agent]}"
|
|
|
|
server[remoteUser]="" # TODO not implemented
|
|
server[redirectRemoteUser]="" # TODO not implemented
|
|
server[authType]="" # TODO not implemented
|
|
|
|
server[serverSignature]=""
|
|
if true; then # TODO add condition setting
|
|
server[serverSignature]="${server[httpHost]} (${server[serverSoftware]})"
|
|
fi
|
|
|
|
placeholder() {
|
|
declare -A tokens
|
|
tokens[path]="${server[scriptName]}"
|
|
tokens[host]="${server[httpHost]}"
|
|
tokens[server]="${server[serverSoftware]}"
|
|
tokens[signature]="${server[serverSignature]}"
|
|
|
|
text="$(cat)"
|
|
|
|
for key in "${!tokens[@]}"; do
|
|
key="$(echo "$key" | sed -e 's/[]\/$*.^|[]/\\&/g')"
|
|
value="$(echo "${tokens[$key]}" | sed -e 's/[]\/$*.^|[]/\\&/g')"
|
|
|
|
text=$(echo "$text" | sed -e "s/\[$key\]/$value/g")
|
|
|
|
done
|
|
|
|
echo "$text"
|
|
}
|
|
|
|
addStatusContainer() {
|
|
i=0
|
|
container="/dev/shm/st-cont-$$-";
|
|
while true; do
|
|
if test -f "$container$i"; then
|
|
i=$(($i+1))
|
|
continue
|
|
fi
|
|
container="$container$i"
|
|
break
|
|
done
|
|
touch "$container"
|
|
echo "$container"
|
|
}
|
|
removeStatusContainer() {
|
|
container="$1"
|
|
rm "$container"
|
|
}
|
|
|
|
isExecutableShell() {
|
|
path="$1"
|
|
if test ! -x "$path" -o ! -f "$path"; then
|
|
return 1
|
|
fi
|
|
ext="$(echo "$path" | awk -F. '{ print $NF }')"
|
|
for i in ${settings[shellExec]}; do
|
|
if test "$ext" = "$i"; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
isExecutableCGI() {
|
|
path="$1"
|
|
if test ! -x "$path" -o ! -f "$path"; then
|
|
return 1
|
|
fi
|
|
ext="$(echo "$path" | awk -F. '{ print $NF }')"
|
|
for i in ${settings[cgiExec]}; do
|
|
if test "$ext" = "$i"; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
isExecutablePHP() {
|
|
path="$1"
|
|
if test ! -x "$path" -o ! -f "$path"; then
|
|
return 1
|
|
fi
|
|
ext="$(echo "$path" | awk -F. '{ print $NF }')"
|
|
for i in ${settings[phpExec]}; do
|
|
if test "$ext" = "$i"; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
cgiExec() {
|
|
export GATEWAY_INTERFACE="CGI/1.1"
|
|
|
|
export AUTH_TYPE="${server[authType]}"
|
|
export DOCUMENT_ROOT="${server[documentRoot]}"
|
|
export HTTP_ACCEPT_CHARSET="${server[httpAcceptCharset]}"
|
|
export HTTP_ACCEPT_ENCODING="${server[httpAcceptEncoding]}"
|
|
export HTTP_ACCEPT_LANGUAGE="${server[httpAcceptLanguage]}"
|
|
export HTTP_ACCEPT="${server[httpAccept]}"
|
|
export HTTP_CONNECTION="${server[httpConnection]}"
|
|
export HTTP_HOST="${server[httpHost]}"
|
|
export HTTPS="${server[https]}"
|
|
export HTTP_USER_AGENT="${server[httpUserAgent]}"
|
|
export QUERY_STRING="${server[queryString]}"
|
|
export REDIRECT_REMOTE_USER="${server[redirectRemoteUser]}"
|
|
export REMOTE_ADDR="${server[remoteAddress]}"
|
|
export REMOTE_HOST="${server[remoteHost]}"
|
|
export REMOTE_PORT="${server[remotePort]}"
|
|
export REMOTE_USER="${server[remoteUser]}"
|
|
export REQUEST_METHOD="${server[requestMethod]}"
|
|
export REQUEST_TIME_FLOAT="${server[requestTimeFloat]}"
|
|
export REQUEST_TIME="${server[requestTime]}"
|
|
export REQUEST_URI="${server[requestURI]}"
|
|
export SCRIPT_FILENAME="${server[scriptFilename]}"
|
|
export SCRIPT_NAME="${server[scriptName]}"
|
|
export SERVER_ADDR="${server[serverAddress]}"
|
|
export SERVER_ADMIN="${server[serverAdmin]}"
|
|
export SERVER_NAME="${server[serverName]}"
|
|
export SERVER_PORT="${server[serverPort]}"
|
|
export SERVER_PROTOCOL="${server[serverProtocol]}"
|
|
export SERVER_SIGNATURE="${server[serverSignature]}"
|
|
export SERVER_SOFTWARE="${server[serverSoftware]}"
|
|
|
|
"$1"
|
|
}
|
|
|
|
|
|
baseSource="$(pwd)/base.source.sh"
|
|
|
|
status=500
|
|
content=""
|
|
declare -A responseHeaders
|
|
responseHeaders[Content-Type]="text/html"
|
|
responseHeaders[Server]="${settings[server]}"
|
|
responseHeaders[Connection]="max=1"
|
|
|
|
type=""
|
|
|
|
if test ! -e "${server[scriptFilename]}"; then
|
|
status=404
|
|
|
|
content="$(placeholder < ./404.html)"
|
|
|
|
type="-"
|
|
elif test "${settings[index]}" = "true" -a -d "${server[scriptFilename]}"; then
|
|
container="$(addStatusContainer)"
|
|
content="$(./index.sh "$baseSource" "$container" "$(declare -p settings)" "$(declare -p server)" "$(declare -p headers)")"
|
|
status=$(head -n 1 "$container")
|
|
if test "$status" = ""; then
|
|
status=200
|
|
fi
|
|
while read line; do
|
|
responseHeaders[$(echo "$line" | awk -F: '{ print $1 }')]="$(echo "$line" | awk '{for (i=2; i<=NF; i++) print $i}')"
|
|
done <<< $(tail -n 1 "$container")
|
|
|
|
removeStatusContainer "$container"
|
|
|
|
type="index"
|
|
elif isExecutableShell "${server[scriptFilename]}"; then
|
|
container="$(addStatusContainer)"
|
|
pushd "$(dirname "${server[scriptFilename]}")" > /dev/null
|
|
content=$("${server[scriptFilename]}" "$baseSource" "$container" "$(declare -p settings)" "$(declare -p server)" "$(declare -p headers)")
|
|
popd > /dev/null
|
|
status=$(head -n 1 "$container")
|
|
if test "$status" = ""; then
|
|
status=200
|
|
fi
|
|
while read line; do
|
|
if test "$(echo "$line" | grep ':')" = ""; then
|
|
continue
|
|
fi
|
|
responseHeaders[$(echo "$line" | awk -F: '{ print $1 }')]="$(echo "$line" | awk '{for (i=2; i<=NF; i++) print $i}')"
|
|
done <<< $(tail -n 1 "$container")
|
|
|
|
removeStatusContainer "$container"
|
|
|
|
type="shell"
|
|
elif isExecutableCGI "${server[scriptFilename]}"; then
|
|
content=""
|
|
status=200
|
|
headerDone=0
|
|
while read line; do
|
|
if test $headerDone = 1; then
|
|
content="${content}${line}"$'\n'
|
|
continue
|
|
fi
|
|
if test "$(echo "$line" | grep ':')" = ""; then
|
|
headerDone=1
|
|
continue
|
|
fi
|
|
if test "$(echo "$line" | awk -F: '{print $1}')" = "Status"; then
|
|
status="$(echo "$line" | awk '{print $2}')"
|
|
continue
|
|
fi
|
|
responseHeaders[$(echo "$line" | awk -F: '{ print $1 }')]="$(echo "$line" | awk '{for (i=2; i<=NF; i++) print $i}')"
|
|
done <<< $( # open subshell
|
|
cgiExec "${server[scriptFilename]}"
|
|
)
|
|
|
|
type="cgi"
|
|
elif isExecutablePHP "${server[scriptFilename]}"; then
|
|
content=""
|
|
status=200
|
|
headerDone=0
|
|
while read line; do
|
|
if test $headerDone = 1; then
|
|
content="${content}${line}"$'\n'
|
|
continue
|
|
fi
|
|
if test "$(echo "$line" | grep ':')" = ""; then
|
|
headerDone=1
|
|
continue
|
|
fi
|
|
if test "$(echo "$line" | awk -F: '{print $1}')" = "Status"; then
|
|
status="$(echo "$line" | awk '{print $2}')"
|
|
continue
|
|
fi
|
|
responseHeaders[$(echo "$line" | awk -F: '{ print $1 }')]="$(echo "$line" | awk '{for (i=2; i<=NF; i++) print $i}')"
|
|
done <<< $( # open subshell
|
|
export REDIRECT_STATUS=1
|
|
cgiExec php-cgi -f "${server[scriptFilename]}"
|
|
)
|
|
|
|
type="php"
|
|
|
|
else
|
|
status=200
|
|
#responseHeaders['Content-Type']="$(file -b --mime-type ${server[scriptFilename]})"
|
|
responseHeaders['Content-Type']="$(mimetype -b "${server[scriptFilename]}")"
|
|
content="$(cat ${server[scriptFilename]})"
|
|
|
|
type="static"
|
|
fi
|
|
|
|
length=$(printf "%s" "$content" | wc -c)
|
|
|
|
if test "${settings[verbose]}" -ge "0"; then
|
|
echo "$(date --rfc-3339=ns) - ${server[remoteAddress]}:${server[remotePort]} - ${headers[Host]}${server[requestURI]} - $type - $status - $length bytes" 1>&2
|
|
fi
|
|
|
|
echo -en "HTTP/1.1 $status $(./statusString.sh $status)\r\n"
|
|
echo -en "Content-Length: $length\r\n"
|
|
for key in ${!responseHeaders[@]}; do
|
|
#echo -en "$(urlencode "$key"): $(urlencode "${responseHeaders[$key]}")\r\n"
|
|
echo -en "$key: ${responseHeaders[$key]}\r\n"
|
|
done
|
|
echo -en "\r\n"
|
|
printf "%s" "$content"
|
|
|