#!/bin/sh

#? NAME
#?       $0 - wrapper script to handle O-Saft in a container image
#?
#? SYNOPSIS
#?       $0
#?       $0 build
#?       $0 usage
#?       $0 save
#?       $0 shell
#?       $0 root
#?       $0 status
#?       $0 inspect
#?       $0 tags
#?       $0 kill
#?       $0 rmi
#?       $0 apk  package
#?       $0 apt  package
#?       $0 sshx
#?       $0 what file
#?       $0 call /path/to/program [arguments]
#?       $0 cp   file-to-be-copied [file-destination]
#?       $0 update file
#?       $0 [OPTIONS] gui
#?       $0 [OPTIONS] [options and commands of o-saft.pl]
#?
#?      Note that own options like -v -id= -tag= must be specified before any
#?      other command or mode.
#?
#? OPTIONS
#?       -h      got it
#        -help   got it
#?       -n      do not execute, just show what would be done
#?       -v      be a bit verbose
#?       +V      show own version (SID)
#?       -id=ID  use container image ID (registry:tag); default: owasp/o-saft
#?       -tag=24 use container image ID with tag; default: (empty)
#?       -name=pattern
#?               use container name (used for kill mode only)
#?       -local  use --network=host to allow container accessing the host
#?               WARNING: see section  SECURITY  below also.
#?       -OSAFT_VERSION=24.09.24  same as OSAFT_VERSION environment variable
#?       -OSAFT_VM_SRC_OSAFT=file same as OSAFT_VM_SRC_OSAFT environment variable
#?       -OSAFT_VM_SHA_OSAFT=feed same as OSAFT_VM_SHA_OSAFT environment variable
#                -OSAFT_VM_SRC_OSAFT= and -OSAFT_VM_SHA_OSAFT= needed for our make
#?       --      pass all remaining parameters to o-saft.pl in image
#?
#? QUICKSTART
#?       The main purpose of this wrapper is to provide a very simple call of
#?       o-saft.pl in a started O-Saft container image.
#?       Example:
#?           $0 +info your.tld
#?
#? DESCRIPTION
#?       This is a multipurpose wrapper to handle O-Saft in a container image.
#?       It can operate in following modes:
#?           build   - build the container image for O-Saft from Dockerfile
#?           save    - save O-Saft container image to o-saft_docker.tar
#?           usage   - print the initial usage message for O-Saft in container
#?           status  - show various information about O-Saft container image
#?           inspect - show O-Saft container image configuration
#?           rmi     - remove O-Saft container image(s)
#?           apk     - add and commit packages in O-Saft container image
#?           tags    - show tags assigned to O-Saft container image
#?           kill    - kill the running O-Saft container
#?           shell   - run a shell (/bin/sh) in container image
#?           root    - run a shell (/bin/sh) as user root in container image
#?           call    - run specified program with arguments in container image
#?           cp      - copy file to /O-Saft directory in the container image,
#?                     then commit changes
#?           update  - unpack file, copy to conatiner's / directory, commit
#?                     WARNING:  experimental implementation,  breaks UID:GID
#?                     of copied file in container
#                      file must be tarball .tar or .tgz, or .zip
#            hacker  - show more information
#?           gui     - start o-saft.tcl in container image (display to host)
#?           sshx    - start o-saft.tcl in container image (tunnel X via ssh)
#?           what    - get SID of specified file
#?           *       - anything else (default): run o-saft.pl in container
#?
#?       This wrapper expects, that the O-Saft container image has the name:
#?           owasp/o-saft
#?       If there are more container images  with the same (registry) name, a
#?       tag can be used to identify the image, see  -tag=TAG  option, i.e.
#?           $0 -tag=24.09.24 +info your.tld
#?       will use the name:  owasp/o-saft:24.09.24 .  The  -id=ID  option can
#?       be used to redefine the (registry:tag) name to be used completely.
#?       For example:
#?           owasp/o-saft:4711
#?       See  ENVIONMENT VARIABLES  below also.
#?
#?       If any other mode than the default is used, please see  WARNING  and
#?       SECURITY  below.
#?
#? TERMINOLOGY
#?
#?   container
#?       The term "container" is used when either Docker or podman is meant.
#?
#?   Docker vs. docker
#?       The term "Docker" is used when the Docker system in general is meant.
#?       The term "docker" is used when the Docker client program is meant.
#?
#?   mode vs. command
#?       The term "mode" is used for our script, which is similar to the term
#?       "command" of Docker or podman.  It distinguishes the commands herein
#?       from those of Docker or podman.
#?
#?   "o-saft.pl in container image"
#?       Means that the program defined by O-Saft container's ENTRYPOINT will
#?       be executed. This can be  o-saft.pl  or  o-saft .
#?
#? MODES
#?       Modes are simply an alias  to call docker or podman  with the proper
#?       commands and options for the  O-Saft container image. Most modes use
#?       for example the command "docker run --rm ...".
#?       This wrapper ensures that the proper  O-Saft container image will be
#?       used by passing the correct  "repository:tag"  image id (default:
#?       owasp/o-saft). This script will exit, if the image owasp/o-saft does
#?       exist (to avoid automatic pull from Docker's repository).
#?
#?       The modes are:
#?
#?   default
#?       In default mode,  all parameters given to this wrapper are passed to
#?       o-saft.pl in the container image. Means that for all existing calls,
#?       the name of the script (usually o-saft.pl) needs simply to be
#?       replaced by  $0 . Example:
#?           o-saft.pl +info your.tld
#?       becomes
#?           $0 +info your.tld
#?       Please see  LIMITATIONS  below also.
#?
#?   gui
#?       Start  o-saft.tcl in the container image with display to the host.
#?
#?   sshx
#?       Start o-saft.tcl in the container image using ssh to tunnel X to the
#?       hosts display.
#?
#?   what
#?       Extract SID of the specified file.
#?
#?   build
#?       This mode builds the container image for O-Saft from the Dockerfile.
#?       If ./Dockerfile is missing following will be used:
#?           https://github.com/OWASP/O-Saft/raw/master/Dockerfile
#?       See  ENVIONMENT VARIABLES below for supported environment variables.
#?
#?   save
#?       Save O-Saft Docker image to ./o-saft_docker-VERSION.tar .
#?
#?   apk
#?   apt
#?       Add and commit the specified packages in O-Saft container image.
#?       Note that this creates a new layer for the O-Saft container image.
#?       For more advanced mode, please refer to  o-saft-docker-dev.
#?       Uses  apk or apt  to add/install the package, depending on the mode.
#?
#?   rmi
#?       Remove all O-Saft images, including tagged images.
#?
#?   tags
#?       Show all assigned tags to O-Saft container image.
#?
#?   kill
#?       Kill a running O-Saft container. Works only if exactly one container
#?       is running. The option --name=pattern  may be used to kill any other
#?       running container.
#?
#?   usage
#?       Print a brief purpose and usage of this script.
#?       This is intended to be called from  within the container image once,
#?       right after the initial build (see Dockerfile).
#?
#?   shell
#?       Start a shell in O-Saft container image.
#?       This is useful because the  ENTRYPOINT  in the image is  o-saft  or
#?       o-saft.pl , which otherwise will always be executed.
#?
#?   root
#?       Same as mode  shell, but login as user root.
#?
#?   cp
#?       Copy file to  /O-Saft directory in the container image, then commits
#?       changes. Only a single file can be copied.
#?
#?   call
#?       Execute specified program with given arguments.  The program must be
#?       found using $PATH or a full path.
#?       This can be useful to retrieve other information from the image, for
#?       example  O-Saft  itself uses it to get the environment variables set
#?       in the image:
#?           $0 call /usr/bin/env
#?
#?   inspect
#?       Print information about O-Saft container image with inspect command.
#?
#?   status
#?       Show various information about O-Saft container image.
#?
#? WARNING
#?       It is highly recommended to  *not* use the created image as base for
#?       other container images. Also do *not* use openssl from the image for
#?       other purpose than O-Saft intends.
#?
#? SECURITY
#?       When working with containers not build with trusted tools under your
#?       control (for example a downloaded Docker image), at least  two types
#?       of security have to be observed.
#?
#?       It's rather difficult to create a secure container, the pulled files
#?       (Dockerfile, image, other sources) must be trusted.
#?
#?       The known security types are:
#?
#?   Security of the host when access from within the container
#?       Most likely the tools in the container cannot access the host or its
#?       services directly. To enable access via network special options must
#?       be used when starting the container, for example:
#?           docker run --network:host ...
#?       There're other ways to allow the container to talk to the host which
#?       are not used by this script and so out of scope of this description.
#?
#?       However, there is the option   -local  which exposes the host to the
#?       container using  --network=host . This is mainly intended for making
#?       tests with a server running on the host.
#?       ####################################################################
#?       ##   DO NOT USE THIS OPTION ON PRODUCTIVE OR SENSITIVE SYSTEMS.   ##
#?       ####################################################################
#?
#?   Security of the created images
#?       When creating containers, wether using build, load or pull, you must
#?       trust the sources: the binary (load or pull) or the fetched (at run-
#?       time) Dockerfile used to build the image.
#?
#?       Update June/2018: Crypto-Miner in Docker images detected:
#?           https://www.fortinet.com/blog/threat-research/yet-another-crypto-mining-botnet.html
#?           https://kromtech.com/blog/security-center/cryptojacking-invades-cloud-how-modern-containerization-trend-is-exploited-by-attackers
#?           https://sysdig.com/blog/detecting-cryptojacking/
#?       That is why a transparent build of the container is prefered.  It is
#?       recommended to build the container for O-Saft using the Dockerfile.
#?
#?   Security of the tools executed from within the image (docker run)
#?       If the image is trustworthy (see before),  the tools executed in the
#?       image may have security impacts. 
#?       The script creates a container for O-Saft which is supposed to check
#?       a target for  SSL/TLS  related issues.  It does not  harm the target
#?       using  penetration or attacking techniques  (except check Heartbleed
#?       vulnerability). So o-saft.pl itself shouldn't be any security issue.
#?       However, to perform the checks it's recommended to install a special
#?       version of OpenSSL. This OpenSSL has functionality enabled, which is
#?       known to be insecure (i. e. SSLv2, SSLv3).
#?
#?       That's why this script uses a transparent method to create an image.
#?       
#? HACKER's INFO
#?       Options for this script are with a single - (dash) only, because the
#?       same options with double  --  exist for  o-saft.pl. See  LIMITATIONS
#?       below also.
#
#        anyname-docker is the same as calling: $0 -docker
#        anyname-podman is the same as calling: $0 -podman
#
#        We do not use shortcut commands for docker like run, rmi, save, etc.
#        because they are depricated according docker documentation (6/2016).
#
#        Keep in mind that docker is picky about the sequence of commands and
#        options. In particular all options must precede the IMAGE argument.
#
#        "docker commit ..." is used without the --message option because the
#        documentation about  --message and/or -m  is ambigious.  Also passed
#        comment text is mainly the same as Docker already reports with the
#        "docker history IMAGE"  command in the  "CREATED BY" column.
#
#        podman instead of docker is used with option  -podman ,  which works
#        flawless (09/2024). Note that the internal (shell) variable contain-
#        ing the executable name of the container command is $docker, even if
#        podman is used.
#
# KNOWN PROBLEMS
#        * Error when building docker image
#            fetch http://dl-cdn.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
#            WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz: temporary error (try again later)
#            fetch http://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/APKINDEX.tar.gz
#            WARNING: Ignoring http://dl-cdn.alpinelinux.org/alpine/edge/community/x86_64/APKINDEX.tar.gz: temporary error (try again later)
#            ERROR: unsatisfiable constraints:
#
#          Reason: backend mirror  dl-cdn.alpinelinux.org  down or not responding
#          Most likely an update of the host's docker package will fix it.
#?
#? LIMITATIONS
#?       The options  -n  -v  -id=ID  and  -h  must be specified  leftmost if
#?       they should apply for this script.
#?       These options can only be passed to o-saft.pl in the container image
#?       if they are preceeded by the  --  parameter.
#?       The option  +VERSION  is not supported for this tool, it's passed to
#?       the container image, use  +V  instead.
#?       Unknown parameters to this script will be treated as arguments to be
#?       passed to o-saft.pl and will terminate internal argument scanning in
#?       this script.
#?       The script calls the program defined by the  ENTRYPOINT in the
#?       container image (which is most likely o-saft).
#?       This script is not intended to call  o-saft.cgi in the container.
#?       However, it is possible to do so:
#?           $0 call o-saft.cgi --cgi [options and commands of o-saft.pl]
#?
#?       Using the GUI from the container image (for example with mode gui)
#?       may return errors like:
#?           Authorization required, but no authorization protocol specified
#?           application-specific initialization failed: couldn't connect to display ":0.0"
#?       Then the calling host  does not allow connections from foreign hosts
#?       to its X-Display. Use the  'xhost' or 'xauth'  to adjust permissions
#?       properly to connect to the display.
#?
#? ENVIONMENT VARIABLES
#?       Following environment variables can be used to pass settings to the
#?       script (used for the build mode only):
#?
#?           OSAFT_VERSION          - version to be passed to Dockerfile
#?           OSAFT_VM_SRC_OSAFT     - URL to o-saft.tgz
#?           OSAFT_VM_SRC_SSLEAY    - URL to Net-SSLeay.tgz
#?           OSAFT_VM_SRC_SOCKET    - URL to IO-Socket.tgz
#?           OSAFT_VM_SRC_OPENSSL   - URL to openssl.tgz
#?           OSAFT_VM_SHA_OSAFT     - checksum of o-saft.tgz
#?           o_saft_docker_name     - contains an image registry name
#?                                    same as -id=ID
#?           o_saft_docker_tag      - contains an image tag
#?                                    same as -tag=TAG
#?
#?       To get the build-in defaults use:
#?           $0 status
#?
#? EXAMPLES
#?       See  SYNOPSIS.
#?
#? SEE ALSO
#?       docker(1), podman(1), o-saft-docker-dev, o-saft.pl, o-saft.tcl
#?
#? VERSION
#?       @(#) o-saft-docker 3.26 25/06/27 19:47:57
#?
#? AUTHOR
#?      17-jul-17 Achim Hoffmann
# -----------------------------------------------------------------------------

#_____________________________________________________________________________
#_____________________________________________ internal variables; defaults __|
SID="3.26"
VERSION="${OSAFT_VERSION:-24.09.24}"
ich=${0##*/}
tag=${o_saft_docker_tag};     # tag=${tag:-17.09.17}
registry=${o_saft_docker_name};
registry=${registry:-owasp/o-saft}
kill_name=
image_name="${registry}"
[ -n "$tag" ] && image_name="${registry}:${tag}"
entrypoint_pl='ENTRYPOINT ["perl", "/O-Saft/o-saft.pl"]';   # default ENTRYPOINT
entrypoint_sh='ENTRYPOINT ["/O-Saft/o-saft"]';              # default ENTRYPOINT
entry__cmd='CMD ["--norc", "--help=docker"]':               # default CMD
network=                      # --network=host  for option -local
try=
exe="o-saft.pl"
dockerfile="https://github.com/OWASP/O-Saft/raw/master/Dockerfile"
docker=$(\command -v docker)
podman=$(\command -v podman)
case "$ich" in
  *-docker)     docker="$docker"; ;;
  *-podman)     docker="$podman"; ;;
esac

#_____________________________________________________________________________
#________________________________________________________ general functions __|

_error  ()      {
	echo "**ERROR: $@"
	return
}

_warn   ()      {
	echo "**WARNING: $@"
	return
}

my_help ()      {
	\sed -ne "s/\$0/$ich/g" -e '/^#?/s/#?//p' $0
	return
}

#_____________________________________________________________________________
#_________________________________________________________ docker functions __|

check_image_name () {
	#? check if image with configured registry:tag exists; exit otherwise
	# This function should be called before most docker/podman commands.
	# This tool does not allow automatic pull by default.
	# Most (all?) Docker commands automatically try to pull the specified
	# image if it does not exist.  More worse, while some Docker commands
	# use any matching registry name  (run without --entrypoint=), others
	# require that the registry:tag name exists (run with --entrypoint=).
	# Unfortunately Docker  does not provide  any option or configuration
	# to inhibit such automatic pulls.
	# This function tries to find existing images matching the configured
	# image (name). The function exits the script if the found image name
	# does not equal the configured image name.
	# Lists all images matching the required one. As "docker image ls -q"
	# reports the image IDs only, duplicate image IDs may be returned.
	# Images which are just tags, return the same image ID.  Hence we can
	# simply filter them using "sort -u".
	# -n does not need the image, just report commands.
	[ -n "$try" ] && \
	 echo "$docker image ls ${image_name} -q |  sort -u |  wc -w" && return
	is_id=`$docker image ls ${image_name} -q | \sort -u | \wc -w`
	[ "${is_id}" -eq 1 ] && return; # found image, return

	[ "${is_id}" -ne 1 ] && \
		_error "given image '${image_name}' does not match found image(s)" # '${is_id}'"
	if [ "${is_id}" -gt 1 ]; then
		for tag in `$docker image ls ${image_name} | \awk '/^REPOSITORY/{next}{print $2}'` ; do
			# varaiable tag is the same as the global one!
			echo "#        consider using  option -id=${image_name}:$tag  or  -tag=$tag"
		done
	fi
	exit 3;     # no image found, exit script
	# return
}

docker_usage () {
	_me=${0##*/}
	# openssl in docker image should be found using path there;
        # for builds with Dockerfile.openssl ist will be /openssl/bin/openssl
	if [ -n "$osaft_vm_build" ]; then
		echo "# $0 called inside container: $osaft_vm_build"
		_openssl="openssl"
		_me=${0##*/}
	else
		echo "# $0 called outside container"
		_openssl="$0 call openssl"
		_me=$0
	fi
	_ciphers=`$_openssl ciphers -V ALL:COMPLEMENTOFALL:aNULL | \wc -l`
	cat << EoDescription

      # openssl:
	$_openssl supports $_ciphers ciphers 

      # o-saft.pl:
	O-Saft container is ready to run o-saft.pl, use:

		podman run --rm -it ${image_name} +info your.tld
		docker run --rm -it ${image_name} +info your.tld
	or:
		$_me  +info your.tld
	or:
		o-saft -docker +info your.tld

EoDescription
	tcl=$(\command -v wish)
	cat << EoDescription
      # o-saft.tcl:
	# run on host:
	However  o-saft.tcl  may be started on the host and then use o-saft.pl
	from the Docker image:

		o-saft.tcl --docker
	or:
		$_me gui
		$_me gui +info your.tld

	# run in Docker image
	The GUI  o-saft.tcl  will only work inside Docker if the packages tcl
	tk and xvfb are installed.

EoDescription
	[ -z "$tcl" ] && echo "                # wish not installed"
	[ -n "$tcl" ] && cat << EoDescription

		$0 apk tcl tk xvfb
		$0 gui
		$0 gui your.tld

EoDescription
	return
}
# TODO: alpine:3.8 misses 'tablelist' there seems to be no package 'tklib'

docker_hacker () {
	cat << EoHacker

	To use the GUI  o-saft.tcl  inside the container, additionl packages
	need to be installed. For Docker image based on alpine, following works:

		$0 apk add tcl tk tklib xvfb

EoHacker
	return
}

docker_inspect () {
	$try $docker image inspect ${image_name}
	return
}

docker_status () {
 	image_id=`$docker image ls -q ${image_name}`
	cat << EoStatus
# using environment ...
#     OSAFT_VERSION      = $OSAFT_VERSION
#     o_saft_docker_name = $o_saft_docker_name
#     o_saft_docker_tag  = $o_saft_docker_tag

# using ...
#     registry name = ${registry}
#     tag           = ${tag}
#     registry:tag  = ${image_name}

# images ...
# $docker image ls -q ${image_name}
#     docker image id for  ${image_name}  is  $image_id

# $docker image ls ${registry}
EoStatus

	# list matching images
	$try $docker image ls ${registry}; # will be listed below again
	# list related (tagged) images
	for _id in  `$docker image ls -q ${registry}` ; do
		$try $docker image ls | \grep "$_id"
	done;
	echo
	echo "# container processes ..."
	echo "# $docker container ls -a"
	$try $docker container ls -a

	echo ""
	echo "# O-Saft container build ..."
	_id=`$0 -n call env`
	echo "# $_id"
	_id=`$0 call env | grep osaft_vm_build`
	if [ -n "$_id" ]; then
		echo "#     $_id"
	else
		echo "#     <<no build variable 'osaft_vm_build' found>>"
	fi
	return
}

docker_build () {
	# build O-Saft Docker image from Dockerfile, mainly using defaults
	# from Dockerfile; cleanup the containers
	_arg1=
	_arg2=
	_arg3=
	_arg4=
	_arg5=
	_tar=./o-saft_docker.tar
	_cfg=./Dockerfile
	[ ! -e $_cfg ] && $try \wget ${dockerfile} -O $_cfg
	# pass environment variables (known by Dockerfile) to docker build
	[ -z "$OSAFT_VERSION" ]        && OSAFT_VERSION="$VERSION"  # use hardcoded VERSION if environment missing
	[ -n "$OSAFT_VERSION" ]        && _arg0="--build-arg OSAFT_VERSION=$OSAFT_VERSION"
	[ -n "$OSAFT_VM_SRC_OSAFT"   ] && _arg1="--build-arg OSAFT_VM_SRC_OSAFT=$OSAFT_VM_SRC_OSAFT"
	[ -n "$OSAFT_VM_SRC_SSLEAY"  ] && _arg2="--build-arg OSAFT_VM_SRC_SSLEAY=$OSAFT_VM_SRC_SSLEAY"
	[ -n "$OSAFT_VM_SRC_SOCKET"  ] && _arg3="--build-arg OSAFT_VM_SRC_SOCKET=$OSAFT_VM_SRC_SOCKET"
	[ -n "$OSAFT_VM_SRC_OPENSSL" ] && _arg4="--build-arg OSAFT_VM_SRC_OPENSSL=$OSAFT_VM_SRC_OPENSSL"
	[ -n "$OSAFT_VM_SHA_OSAFT"   ] && _arg5="--build-arg OSAFT_VM_SHA_OSAFT=$OSAFT_VM_SHA_OSAFT"
	# set environment for docker before v23
	DOCKER_BUILDKIT=1 ; export DOCKER_BUILDKIT
	$try $docker image build -f ./Dockerfile --force-rm --rm -t ${image_name} \
		$_arg0 $_arg1 $_arg2 $_arg3 $_arg4 $_arg5 .	&& \
	$try $docker image save  -o $_tar ${image_name}	&& \
	$try $docker image rm    `$try docker image ls -q ${image_name}` && \
	$try $docker image load  -i $_tar && \
	$try \rm                    $_tar && \
	[ "test" = "$tag" ] && return
	# additional convenient registry tags if not -tag=test
	$try $docker image tag   ${image_name} o-saft	&& \
	$try $docker image tag   ${image_name} ${image_name}:$OSAFT_VERSION
	return
}

docker_save  () {
	# save O-Saft Docker image to o-saft_docker.VERSION.tar
	# compute version form image's tag
	for _tag in $($docker image inspect ${image_name} --format '{{.RepoTags}}'); do
		_tag=${_tag##[}     # remove leading [
		_tag=${_tag%%]*}    # remove trailing ]
		case "$_tag" in
		  *:latest) continue; ;;    # keeps last found tag in $_tag
		  *) break; ;;              # first none :latest tag is ok
		esac
	done
	_version=${_tag##*:}        # get anything right of :
	_version=${_version##*/}    # / not allowed, hence use anything right of it
	[ -z "$_version" ] && _version="$VERSION"   # use hardcoded VERSION if not found
	_tar=./o-saft_docker.$_version.tar
	[ -e "$_tar" ] && _error "'$_tar' exists; exit" && exit 2
	$try $docker image save ${image_name} -o $_tar
	$try chmod -w $_tar
	return
}

docker_osaft () {
	# this is the command for the default mode, passing all parameters
	# expects a proper ENTRYPOINT like /O-Saft/o-saft.pl in the image
	$try $docker container run --rm $network -i  ${image_name} $@
	return
}

docker_gui   () {
        # start GUI in docker with display to host
	# --entrypoint= not really necessary if ENTRYPOINT=/O-Saft/o-saft
echo "# network = $network"
	$try $docker container run --rm $network -i  --entrypoint=/O-Saft/o-saft.tcl \
		-e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix ${image_name} $@
	return
}

docker_sshx  () {
	_ssh=`$docker port ${image_name} 22`
	if [ -z "${_ssh}" ]; then
		_error "no ssh port available for '${image_name}'"
	else
		# use "ip addr" to get docker's ip, should return something like:
		#   6: docker0    inet 172.17.0.1/16 brd 172.17.255.255 scope global ...
		# TODO: returns IP of last interface if more than on exists
		_ip=`\ip -4 -o addr | \awk '($2~/docker/){sub("/..","",$4);print $4}'`
		$try \ssh user@${_id} -p ${_ssh} /O-Saft/o-saft.tcl
	fi
	return
}

docker_call  () {
	cmd="$1"
	shift
	$try $docker container run --rm $network -it --entrypoint=$cmd    ${image_name} $@
	return
}

docker_shell () {
	$try $docker container run --rm $network -it --entrypoint=/bin/sh ${image_name}
	return
}

docker_root  () {
	$try $docker container run --rm $network -it --entrypoint=/bin/sh --user root ${image_name}
	return
}

docker_cp    () {
	src="$1"
	dst="${2-.}"
	_id="o-saft-cp" # temporary container ID
	# create a new container with a dummy command; exit if exists or fails
	$try $docker container run   --name ${_id} ${image_name} +VERSION || exit 4
	$try $docker container cp    $src   ${_id}:/O-Saft/$dst
	$try $docker container commit       ${_id} ${image_name}
	$try $docker container rm           ${_id}
	#
	# if we need to reset the entrypoint:
	# $try $docker container commit       \
	#	--change='ENTRYPOINT ["perl","/O-Saft/o-saft.pl"]' \
	# or:
	# $try $docker container commit       \
	#	--change='ENTRYPOINT ["/O-Saft/o-saft"]' \
	#
	return
}

docker_update (){
	src="$1"
	tgz="${src##*/}"
	tmp=/tmp/$ich.$$.$USER
	_id="o-saft-upd" # temporary container ID
	# executing multiple commands in a container is a pain, hence update
	# is done as follows (other variants see VERSION 3.16):
	#       unpack the tarball on the host which create O-Saft directory
	#       copy O-Saft directory to container
	#       commit changes in container
	#       delete O-Saft directory on host
	# TODO: strange behaviour in docker and podman:
	#       docker: copied files and directory have UID:GID of local system
	#       podman: copied files and directory have UID:GID o-saft
	#       option "--user root" works with podman only
	$try \mkdir $tmp || exit 3
	    # mkdir alredy printed error message
	$try \tar -C $tmp -xf $tgz
	# create a new container with a dummy command; exit if exists or fails
	$try $docker container run   --name ${_id} ${image_name} +VERSION || exit 4
	$try $docker container cp $tmp/O-Saft ${_id}:/
	    # podman cp --overwrite # should be used
	$try $docker container commit       ${_id} ${image_name}
	$try $docker container rm           ${_id}
	$try \rm -rf $tmp
	docker_call INSTALL.sh --check=SID --changes
	    # compares SID of files with those in o-saft.rel; should be empty
	return
}

docker_apk  ()  {
	_id="o-saft-apk"
	# after commit the entrypoint is changed, hence the original one is
	# set again with commit's --change option; unfortunately hardcoded
	$try $docker container run -t --user root --entrypoint=/sbin/apk \
				--name      ${_id} ${image_name} add --no-cache $@ \
		|| exit 4
	$try $docker container commit \
		--change="${entrypoint_sh}" --change="${entry__cmd}" \
					    ${_id} ${image_name}
	$try $docker container rm           ${_id} 
	return

	# TODO: following more cleaner method does not work (reason yet unknown)
	$try $docker container run  -d --name ${_id} ${image_name} +VERSION || exit 4
	$try $docker container exec -t --user root ${_id} /sbin/apk add --no-cache $@
	$try $docker container commit       ${_id} ${image_name}
	$try $docker container rm           ${_id} 
	return
}

docker_apt  ()  {
	_id="o-saft-apt"
	$try $docker container run -t --user root --entrypoint=/usr/bin/apk \
				--name      ${_id} ${image_name} install --no-cache $@ \
		|| exit 4
	$try $docker container commit \
		--change="${entrypoint_sh}" --change="${entry__cmd}" \
					    ${_id} ${image_name}
	$try $docker container rm           ${_id} 
	return
}

docker_rmi  ()  {
	# parameters can only be the options for Docker's rmi command
	#dbx# echo "# registry: ${registry} # image_name: ${image_name}"
	_ids=`$docker image ls -q ${registry}` # ${image_name}
	echo "# image IDs to be deleted: $_ids "
	# need to find all images,  including referenced images, this is done
	# with "images" command, then extract all lines with matching IDs,
	# only these IDs are deleted if their tag matches the -tag=* value
	# Note: "docker images"  should return newest image on top, hence all
	#       images which just represent a tag (alias) come first and will
	#       be deleted first
	$docker image ls | while read _any ; do
		# expect lines like:
		# REPOSITORY              TAG     IMAGE ID      CREATED         SIZE
		# localhost/owasp/o-saft  test    355c01d5c842  27 minutes ago  93 MB
		for _id in $_ids ; do
			_tag=`echo "$_any" | \awk '("'$_id'"==$3){print $2}'`
			_img=`echo "$_any" | \awk '("'$_id'"==$3){print $3}'`
			#dbx# echo "# ID  $_any : $_id : $_img"
			[ -n "$tag" -a "$tag" != "$_tag" ] && continue  # wrong tag
			[ -z "$_img" ] && continue  # other image
			$try $docker image rm -f $@ $_img
		done
	done
	# FIXME: $try $docker image rm ${registry}
	#        probaly only necessary if no -tag= was given
	return
}

docker_tags ()  {
	# show tags assigned to O-Saft container image
	$try $docker image inspect ${image_name} --format '{{.RepoTags}}'
	return
}

docker_kill ()  {
	#dbx# echo "# registry: ${registry} # kill_name: ${kill_name}"
	filter="ancestor=${registry}"
	[ -n "$kill_name" ] && filter="name=$kill_name"
	_ids=`$docker container ls -q --filter $filter`
	set - $_ids
	if [ 1 -eq $# ]; then
		#dbx# echo "# container IDs to be killed: $_ids "
		$try $docker container kill $_ids
	else
		[ -n "$kill_name" ] && filter="name=" # empty name is same as -a
		# no $try here
		$docker container ls --filter $filter
		_error "not exactly one container found, none killed"
	fi
	return
}

#_____________________________________________________________________________
#________________________________________________________________ arguments __|

# first check for options
while [ $# -gt 0 ]; do
	case "$1" in
	  -h | -help | -usage | '-?' | '/?'  | '-/')  my_help;  exit 0; ;;
	  -n)       try=echo; ;;
	  -v | -x)  set -x  ; ;;
	  +V)       echo $SID ; exit 0; ;;
	  -docker)  docker="$docker"  ; ;; # FIXME: fails with: $0 -podman -docker
	  -podman)  docker="$podman"
		    _warn "podman not tested, may work ..."
	            ;;
	  -local)   network="--network=host"; ;;
	  -tag=*)   tag="${1#*=}"; image_name="${registry}:$tag"; ;;
	  -id=*)    image_name="${1#*=}" ; ;;
	  -name=*)  kill_name="${1#*=}"  ; ;;
#	  kill=*)   kill_name="${1#*=}"  ; mode=kill; ;;
	  -OSAFT_VERSION=*)      OSAFT_VERSION="${1#*=}"     ; ;;
	  -OSAFT_VM_SRC_OSAFT=*) OSAFT_VM_SRC_OSAFT="${1#*=}"; ;;
	  -OSAFT_VM_SHA_OSAFT=*) OSAFT_VM_SHA_OSAFT="${1#*=}"; ;;
	  *)        break;  ;;
	esac
	shift
done

#_____________________________________________________________________________
#_____________________________________________________________________ main __|

# don't run inside docker
case "$1" in
  'hacker') docker_hacker;  exit 0; ;;
  'usage')  docker_usage;   exit 0; ;;
  *)        if [ -n "$osaft_vm_build" ]; then
		_error "cannot run inside docker image; exit"
		exit 1
	    fi
	    ;;
esac

# avoid other errors if docker program missing
[ -z "$docker" ] && _error "'docker|podman' not found; exit" && exit 2

# some modes do not need to check existing images
case "$1" in
  'build')  ;;
  'kill')   ;;                  # requires containers only
  *)        check_image_name;   # may exit, see description there
esac

# NOTE: All functions may use ${registry} and ${image_name}, which are global
#       variables. We do not pass these values as parameters to the functions
#       because we want $@ (all parameters of this script) in the functions
while [ $# -gt 0 ]; do
	mode="$1"
	shift
	case "$mode" in
	  'build')  docker_build;   exit 0; ;;
	  'save')   docker_save;    exit 0; ;;
	  'call')   docker_call $@; exit 0; ;;
	  'sshx')   docker_sshx $@; exit 0; ;;
	  'what')   docker_call sed -n -e "s/.*\(@(#).*\)/\1/p" $@; exit 0; ;;
	  'gui')    docker_gui  $@; exit 0; ;;
	  'cp')     docker_cp   $@; exit 0; ;;
	  'update') docker_update $@; exit 0; ;;
	  'apk')    docker_apk  $@; exit 0; ;;
	  'rmi')    docker_rmi  $@; exit 0; ;;
	  'tags')   docker_tags;    exit 0; ;;
	  'kill')   docker_kill $@; exit 0; ;;
	  'root')   docker_root;    exit 0; ;;
	  'shell')  docker_shell;   exit 0; ;;
	  'inspect')docker_inspect; exit 0; ;;
	  'status') docker_status;  exit 0; ;;
#          -registry=*) registry=  # not supported, as this is for O-Saft only
          '--')     shift; break; ;;
          *)               break; ;;
	esac
done

if [ -z "$mode" ]; then
	# did not get any argument, print own usage
	my_help
	echo
	_warn "'$0' called without parameters; nothing done."
	echo
	exit 2
fi

docker_osaft "$mode" $@

exit
