Re: Request for a Lockdown option

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

 



P.S.:

Steffen Nurpmeso wrote in
 <20240707025234.j3oUaPFH@steffen%sdaoden.eu>:
 |Steffen Nurpmeso wrote in
 | <20240704180538.iV4uex29@steffen%sdaoden.eu>:
 ||Simon Josefsson wrote in
 || <87jzi1fg24.fsf@xxxxxxxxxxx>:
 |||Jochen Bern <Jochen.Bern@xxxxxxxxx> writes:
 |||> (And since you mention "port knocking", I'd like to repeat how fond I
 |||> am of upgrading that original concept to a single-packet
 |||> crypto-armored implementation like fwknop.)
 |||
 |||I am reluctantly considering to use some kind of port knocking mechanism
 |||on some machines, however I really don't want to carry around shared
 ...
 |||Does anyone know of any implementation that allows me to configure a
 |||PGP/SSH/FIDO/TPM/whatever public key on the server side, and it then
 |||only listens to signed port knocks from the corresponding private keys?
 | ...
 ||No, but for many years i do have a super simple port-knock server
 ||to do the I/O plus sh(1)ell based client which can do .. whatever.
 | ...
 ||With the possibilities that ssh-keygen -Y sign|verify have added,
 ||one could easily adapt the server and client to send "user-name
 ||MSG", so that the server could look into authorized_keys of
 ||user-name and verify MSG, whatever that is.
 |
 |Hey!  That vision of yours, in conjunction with that -Y
 |possibility of ssh-keygen thrilled me so much i wrote a draft.
 ...

So no, despite that this did not freeaddrinfo() and did not
correctly set exit status twice (in early program state), i do not
like TCP and think TLS in addition makes it worse, with these
multiple packets back and forth.

I thus have built upon my old thing that i use for years, and will
attach it.  It has a very simple UDP server (-lsocket on
OpenIndiana when doing "CC -o /sbin/s-port-knock-bin), and
massively changed the script to produce packets like

 1. password encrypted by X509 pubkey (base64) + LF
 2. LF (gives as room to place a NUL upon receive)
 3. SSH signature cipher-encrypted with password in 1. (base64) + LF

Ie after placing some SSH principals in /tmp/.Zsigs,

  cd /tmp/
  gcc -o ./zt ./s-port-knock-bin.c
  ./s-port-knock.sh create-server-key .Zkey

we can do

  PORT_KNOCK_BIN=/tmp/zt ./s-port-knock.sh \
    start-server -v 45045 \
    /tmp/s-port-knock.sh /tmp/.Zkey-pri.pem /tmp/.Zsigs

and in another window

  PORT_KNOCK_BIN=/tmp/zt ./s-port-knock.sh \
    knock localhost 45045 .Zkey-pub.pem SOME-PUB-SSH-KEY

and if that key is in .Zsigs it works.

One can create a MAGIC and overwrite the block and allow hooks by
starting the server (/ the client) with the environment variable
PORT_KNOCK_RC pointing to some file.  The environment is not
cleared so that is passed through.  Ie

  MAGIC=hallo
  act_block() { echo >&2 '.XRC blocking '$1; }
  act_allow() { echo >&2 '.XRC allowing '$1', principal: '$2; }

The magic is what SSH with create a signature for, fwiw.

Oh, and this does not work on OpenBSD for now, because they do not
know about the AI_V4MAPPED flag, .. wait .., i quickly added
C preprocessor shims.  However, (then) only IPv6 is supported for
now, there (untested, thus).  On FreeBSD it seems you need to set 

  sysctl net.inet6.ip6.v6only=0

(look for rc.conf:ipv6_ipv4mapping="NO" and change, maybe).
On OpenIndiana, despite -lsocket, one should fill in the OPENSSL
variable with the right path right away, like this:

  PORT_KNOCK_BIN=/tmp/zt OPENSSL=/usr/openssl/3.1/bin/openssl \
    ./s-port-knock.sh knock localhost 45045 \
      /tmp/.Zkey-pub.pem SSH-PUBKEY

the rest works out of the box.

Anyhow, this approach i like much better than TLS.
It is a bit ugly that ssh-keygen does not give access to the PEM
version of the OpenSSL private key, on the other hand encryption
only works for RSA (in the western world, and libsodium, on the
other hand, would allow this for Ed25519 it seems, too, and i also
had a quick look at the report that claims this is a save thing
to do, last week, but i am not a cryptographer and thus i would
not do that, despite the fact that this is libsodium only; yes,
there is a OpenSSL thing open for long, but it seems they talk(ed)
him down .. i dunno, really).  And the chinese algorithm, that
much is plain, cannot be generated by OpenSSL (yet), and not
portably anyway.

I will attach these, i like them.  Maybe i find time to make the
OpenBSD variant work for IPv4 and IPv6, but i think it needs two
sockets and select(2), so this is a bit of work.

Ciao, and a nice Sunday everybody.

--steffen
|
|Der Kragenbaer,                The moon bear,
|der holt sich munter           he cheerfully and one by one
|einen nach dem anderen runter  wa.ks himself off
|(By Robert Gernhardt)
#!/bin/sh -
#@ s-port-knock.sh: port knock user interface (for s-port-knock-bin.c server).
#@ Requires (modern) openssl and ssh-keygen with -Y support.
#
syno() {
	echo >&2 'SYNOPSIS: '$0' create-server-key filename-prefix [rsa[:bits]]'
	echo >&2 '          - generate *-{pri,pub}.pem, -pub.pem is needed by clients'
	echo >&2
	echo >&2 'SYNOPSIS: '$0' knock host port pubkey ssh-client-pubkey'
	echo >&2 '          - knock for ssh-client-pubkey at server (host:port, pubkey)'
	echo >&2 '            This requires, in $PATH: bash(1) or '$0
	echo >&2 '          - with $DEBUG: save encrypted key and signature there, instead'
	echo >&2
	echo >&2 'SYNOPSIS: '$0' verify ip-address [prikey allowed-sigs-db (enc-key enc-sig)]'
	echo >&2 '          - (called from server); if prikey and allowed-sigs-db are given'
	echo >&2 '            verify enc-key and enc-sig, else just block IP'
	echo >&2 '          - with $DEBUG set: fetch enc-key and enc-sig from $DEBUG, instead'
	echo >&2
	echo >&2 'SYNOPSIS: '$0' start-server [-v] port cmd-path prikey allowed-sigs-db'
	echo >&2 '           - port is port where to listen on (>0, <65536)'
	echo >&2 '           - cmd-path is the path to a script to run, likely '$0
	echo >&2 '           - prikey and allowed-sigs-db as for "verify"'
	echo >&2 '           - Usually started via start-stop-daemon or so, if -v is used'
	echo >&2 '             you should then pass --no-close and --output additionally to'
	echo >&2 '             --start --background --make-pidfile --pidfile XY --exec '$0
	echo >&2
	echo >&2 'The PORT_KNOCK_RC environment variable may specify a readable file to include.'
	echo >&2 'It may set $MAGIC to include a string in signatures, and overwrite defaults of'
	echo >&2 '          act_block() { echo >&2 "blocking "$1; }'
	echo >&2 '          act_allow() { echo >&2 "allowing $1, principal: $2"; }'
	exit 64 # EX_USAGE
}
#
# 2020 - 2024 Steffen Nurpmeso <steffen@xxxxxxxxxx>
# Public Domain

: ${PORT_KNOCK_BIN:=/usr/sbin/s-port-knock-bin}

: ${RSA_DEFBITS:=3072}
: ${RSA_MINBITS:=1024}

: ${SHELL:=/bin/sh}
: ${AWK:=awk}
: ${OPENSSL:=openssl}

: ${TMPDIR:=/tmp}
: ${ENV_TMP:="$TMPDIR:$TMP:$TEMP"}

# >8 -- 8<

: ${DEBUG:=}

# For heaven's sake auto-redirect on SunOS/Solaris, and for pipefail
if [ -z "$__PORT_KNOCK_UP" ]; then
	if [ $# -eq 0 ] || [ "$1" = -h ] || [ "$1" = --help ]; then
		syno
	elif [ -d /usr/xpg4 ]; then
		if [ x"$SHELL" = x/bin/sh ]; then
			__PORT_KNOCK_UP=y PATH=/usr/xpg4/bin:${PATH} SHELL=/usr/xpg4/bin/sh
			printf >&2 'WARNING: SunOS/Solaris, '
		fi
	elif ( set -o pipefail ) >/dev/null 2>&1; then
		set -o pipefail
	else
		if [ -x /bin/bash ]; then
			__PORT_KNOCK_UP=y SHELL=/bin/bash
		elif [ -x /usr/bin/bash ]; then
			__PORT_KNOCK_UP=y SHELL=/usr/bin/bash
		else
			# TODO too lazy for es=$(exec 3>&1 1>&2; BLA | { XY; echo $? >&3; } | BLA) massage
			echo >&2 'WARNING: shell does not support "set -o pipefail", this HIDES ERRORS (TODO..)'
		fi
		[ -n "$__PORT_KNOCK_UP" ] && printf >&2 'WARNING: "set -o pipefail" unsupported, '
	fi
	if [ -n "$__PORT_KNOCK_UP" ]; then
		printf >&2 'redirecting through $SHELL=%s\n' "$SHELL"
		export __PORT_KNOCK_UP PATH SHELL
		exec $SHELL "$0" "$@"
	fi
elif ( set -o pipefail ) >/dev/null 2>&1; then
	set -o pipefail
fi

LC_ALL=C
EX_DATAERR=65 EX_CANTCREAT=73 EX_TEMPFAIL=75

umask 077

# PORT_KNOCK_RC defaults
MAGIC=
act_block() { echo >&2 'blocking '$1; }
act_allow() { echo >&2 'allowing '$1', principal: '$2; }

tmp_dir= tmp_file=  __tmp_no=1 __tmpfiles=
tmp_file_new() {
	if [ -z "$tmp_dir" ]; then
		_tmp_dir() {
			i=$IFS
			IFS=:
			set -- $ENV_TMP
			IFS=$i
			# for i; do -- new in POSIX Issue 7 + TC1
			for tmp_dir
			do
				[ -d "$tmp_dir" ] && return 0
			done
			tmp_dir=$TMPDIR
			[ -d "$tmp_dir" ] && return 0
			echo >&2 'Cannot find a temporary directory, please set $TMPDIR'
			exit $EX_TEMPFAIL
		}
		_tmp_dir

		trap "exit $EX_TEMPFAIL" HUP INT QUIT PIPE TERM
		trap "trap \"\" HUP INT QUIT PIPE TERM EXIT; rm -f \$__tmpfiles" EXIT
	fi

	while :; do
		tmp_file="$tmp_dir/port-knock-$$-$__tmp_no.dat"
		(
			set -C
			> "$tmp_file"
		) >/dev/null 2>&1 && break
		__tmp_no=$((__tmp_no + 1))
	done
	__tmpfiles="$__tmpfiles $tmp_file"
}

ossl() (
	set +e

	x=
	while :; do
		"$OPENSSL" "$@"
		[ $? -eq 0 ] && return 0

		echo >&2
		echo >&2 '$OPENSSL='$OPENSSL' seems incompatible; is it an old version?'
		echo >&2 '  Command was: '$*
		if [ -x /usr/openssl/3.1/bin/openssl ]; then
			x=/usr/openssl/3.1/bin/openssl
		elif [ -x /usr/openssl/1.1/bin/openssl ]; then
			x=/usr/openssl/1.1/bin/openssl
		elif [ -x /usr/pkg/bin/openssl ]; then
			x=/usr/pkg/bin/openssl
		elif [ -x /opt/csw/bin/openssl ]; then
			x=/opt/csw/bin/openssl
		fi
		if [ x = x"$x" ] || [ x"$x" = x"$OPENSSL" ]; then
			echo >&2 'Please place version >= 1.1.0 (early) in $PATH, or $OPENSSL=, rerun.'
			exit $EX_DATAERR
		fi
		OPENSSL=$x
		echo >&2 'I will try $OPENSSL='$OPENSSL' instead; restarting operation ...'
		echo >&2
	done
)

incrc() {
	[ -n "$PORT_KNOCK_RC" ] && [ -r "$PORT_KNOCK_RC" ] && . "$PORT_KNOCK_RC"
}

case "$1" in
create-server-key)
	[ $# -gt 3 ] && syno
	fprefix=$2

	algo=rsa opt=
	[ $# -gt 2 ] && algo=$3
	case $algo in
	rsa|rsa:*)
		bits=${algo##*:}
		algo=${algo%%:*}
		if [ "$bits" = "$algo" ] || [ -z "$bits" ]; then
			bits=$RSA_DEFBITS
		else
			i=${bits#${bits%%[!0-9]*}}
			i=${i%${i##*[!0-9]}} # xxx why?
			if [ -n "$i" ]; then
				echo >&2 'RSA bits is not a number'
				exit $EX_DATAERR
			fi
			if [ $bits -lt $RSA_MINBITS ]; then
				echo >&2 'RSA bits insufficient (RFC 8301), need at least '$RSA_MINBITS
				exit $EX_DATAERR
			fi
		fi
		opt="-pkeyopt rsa_keygen_bits:$bits"
		;;
	*) syno;;
	esac

	# We do not want to overwrite
	(
		set -C
		> "$fprefix"-pri.pem
		> "$fprefix"-pub.pem
	) || exit $EX_CANTCREAT

	set -e
	ossl genpkey -out "$fprefix"-pri.pem -outform PEM -algorithm $algo $opt
	ossl pkey -pubout -out "$fprefix"-pub.pem -outform PEM < "$fprefix"-pri.pem

	echo 'Server keys stored in'
	echo '   '"$fprefix"-pri.pem
	echo '   '"$fprefix"-pub.pem
	echo 'Server also needs a ssh-keygen(1) allowed_signers_file in format'
	echo '  IDENT(ie, email address) KEYTYPE PUBKEY [COMMENT]'
	echo '(ie, authorized_keys format but prefixed by an IDENT)'
	;;
knock)
	[ $# -ne 5 ] && syno

	if [ -n "$DEBUG" ]; then
		kpk= kexe=
	else
		incrc
		kpk= kexe=
		if [ -x "$PORT_KNOCK_BIN" ]; then
			kpk=y
			kexe=$PORT_KNOCK_BIN
		else
			set +e
			kexe=$(command -v bash 2>/dev/null)
			if [ $? -ne 0 ]; then
				echo >&2 'Knocking requires bash in $PATH; or '$PORT_KNOCK_BIN
				syno
			fi
			set -e
		fi
	fi

	k=$(dd if=/dev/urandom bs=64 count=1 2>/dev/null | tr -cd 'a-zA-Z0-9_.,=@%^+-')
	if [ $? -ne 0 ]; then
		echo >&2 'Failed to create a random key'
		exit $EX_TEMPFAIL
	fi
	ek=$(echo "$k" | ossl pkeyutl -encrypt -pubin -inkey "$4" | ossl base64 | tr -d '\012 ')
	if [ $? -ne 0 ]; then
		echo >&2 'Failed to pkeyutl encrypt'
		exit $EX_TEMPFAIL
	fi

	es=$(printf '%s' "$MAGIC" | ssh-keygen -Y sign -n pokno -f "$5" 2>/dev/null |
		sed -Ee '/^-+BEGIN SSH/d;/^-+END SSH/d;s/$/ /' | tr -d '\012 ' |
		ossl enc -aes256 -pass "pass:$k" -pbkdf2 -e -A -a)
	if [ $? -ne 0 ]; then
		echo >&2 'Failed to encrypt signature'
		exit $EX_TEMPFAIL
	fi

	if [ -n "$DEBUG" ]; then
		set -e
		{ echo "$ek"; echo "$es"; } > "$DEBUG"
	else
		wW=
		if (ping -c1 -w1 localhost >/dev/null 2>&1); then
			wW=-w1
		elif (ping -c1 -W500 localhost >/dev/null 2>&1); then
			wW=-W1000
		fi

		while :; do
			printf 'knocking ... '
			if [ -n "$kpk" ]; then
				"$kexe" client "$3" "$2" "$ek" "$es"
			else
				</dev/null bash -c '
					set -e
					printf "%s\n\n%s\n" "'"$ek"'" "'"$es"'" > /dev/udp/"'"$2"'"/"'"$3"'"
				'
			fi
			[ $? -ne 0 ] && break
			echo 'sent'
			sleep 1
			ping -c1 $wW "$2" && break
			echo 'Did not work out, sleeping 60 seconds and retrying'
			sleep 60
		done
	fi
	;;
verify)
	if [ -z "$DEBUG" ]; then
		[ $# -eq 2 ] || [ $# -eq 6 ] || syno
		incrc
		if [ $# -eq 6 ]; then
			ek=$5 es=$6
		fi
	elif [ $# -eq 2 ]; then
		:
	else
		[ $# -eq 4 ] || syno
		if [ ! -f "$DEBUG" ]; then
			echo >&2 'The result of "knock" does not exist: '$DEBUG
			syno
		fi
		{
			read ek
			read es
		} < "$DEBUG"
	fi

	if [ $# -eq 2 ]; then
		act_block "$2"
	else
		# (of course if pkeyutl fails *after* reading off the pipe this will not do)
		tmp_file_new
		dk=$(echo "$ek" | ossl base64 -d | ossl pkeyutl -decrypt -inkey "$3")
		if [ $? -ne 0 ]; then
			act_block "$2"
		else
			echo "$es" | ossl enc -aes256 -pass "pass:$dk" -pbkdf2 -a -d | {
				printf '%s\n' '-----BEGIN SSH SIGNATURE-----'
				cat
				printf '\n%s\n' '-----END SSH SIGNATURE-----'
			} > "$tmp_file"

			p=$(ssh-keygen -Y find-principals -s "$tmp_file" -f "$4")
			if [ $? -eq 0 ]; then
				if [ -z "$MAGIC" ]; then
					act_allow "$2" "$p"
				elif printf '%s' "$MAGIC" |
						ssh-keygen -Y verify -n pokno -I "$p" -s "$tmp_file" -f "$4"; then
					act_allow "$2" "$p"
				else
					act_block "$2"
				fi
			else
				act_block "$2"
			fi
		fi
	fi
	;;
start-server)
	shift
	[ $# -eq 4 ] || [ $# -eq 5 ] || syno
	incrc
	v=
	if [ $# -eq 5 ]; then
		v=$1
		shift
	fi
	exec "$PORT_KNOCK_BIN" $v server "$@"
	;;
*)
	echo >&2 'Invalid command: '$1
	syno
	;;
esac

exit 0
# s-sht-mode
/*@ s-port-knock-bin.c: C backend for s-port-knock.sh; please see there.
 *@ TODO - capsicum / pledge/unveil (fork from server a forker process that forks+execv the command)
 *
 * Copyright (c) 2020 - 2024 Steffen Nurpmeso <steffen@xxxxxxxxxx>.
 * SPDX-License-Identifier: ISC
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#define _GNU_SOURCE

#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

/* For SO_{SND,RCV}TIMEO and SO_LINGER */
#define a_TIMEOUT_SECS 42

/* Largest possible pubkey encryption + 1 + 1 + SSH signature (ED25519=~295+, RSA=~1560+) + 1 +1; packet is
 *	1. password encrypted by X509 pubkey (base64) + LF
 *	2. LF (gives as room to place a NUL upon receive)
 *	3. SSH signature cipher-encrypted with password in 1. (base64) + LF */
#define a_BUF_LEN (2048 + 1024)

/* Minimum bytes the encrypted SSH signature should have (do not even try to decrypt it, block sender).
 * ED25519 encrypted with -aes256 is 273 bytes */
#define a_SIG_LEN_MIN 256

/* */
#define a_BIN_SH "/bin/sh"

/*  >8 -- 8< */

enum{
	a_EX_OK,
	a_EX_ERR,
	a_EX_USAGE = 64,
	a_EX_DATAERR = 65,
	a_EX_NOHOST = 68,
	a_EX_UNAVAILABLE = 69,
	a_EX_OSERR = 71,
	a_EX_IOERR = 74
};

static int a_verbose;
static int volatile a_sig_seen;

static int a_server(int port, char const *cmd_path, char const *privkey, char const *sigpool);
static int a_client(char const *host, char const *port, char const *enckey, char const *encsig);

static long a_fork(void);
static void a_sig_hdl(int sig);

int
main(int argc, char **argv){
	char const *prog;
	int es, srv, portno;

	es = 1;
	prog = (argc == 0) ? "port-knock" : argv[0];

	fclose(stdin);
	fclose(stdout);

	if(argc > 0 && !strcmp(argv[1], "-v")){
		a_verbose = 1;
		--argc, ++argv;
	}

	if(argc != 6)
		goto jesyn;

	if(!strcmp(argv[1], "server"))
		srv = 1;
	else if(!strcmp(argv[1], "client"))
		srv = 0;
	else
		goto jesyn;

	portno = atoi(argv[2]);
	if(portno <= 0 || portno > 65535){
		fprintf(stderr, "Bad port (must be >0 and <65536): %s -> %d\n", argv[3], portno);
		goto jesyn;
	}

	/* TODO [chroot], pledge/unveil */
	if(chdir("/") == -1){
		fprintf(stderr, "Cannot chdir(2) to /\n");
		goto jleave;
	}

	es = srv ? a_server(portno, argv[3], argv[4], argv[5]) : a_client(argv[3], argv[2], argv[4], argv[5]);
jleave:
	return es;

jesyn:
	fprintf(stderr,
		"Synopsis: %s [-v] server port cmd-path prikey allowed-sigs-db\n"
		"Synopsis: %s [-v] client port host enckey encsig\n",
		prog, prog);
	goto jleave;
}

static int
a_server(int portno, char const *cmd, char const *privkey, char const *sigpool){
	char dbuf[a_BUF_LEN], nbuf[INET6_ADDRSTRLEN +1];
	char const *argv[9];
	struct sigaction siac;
	struct sockaddr_in6 soa6;
	socklen_t soal;
	int sofd, es;

	if(!a_verbose)
		freopen("/dev/null", "w", stderr);

	while((sofd = socket(AF_INET6, SOCK_DGRAM, 0)) == -1){
		es = errno;
		if(es != EINTR){
			fprintf(stderr, "Server socket creation failed: %s\n", strerror(es));
			es = a_EX_OSERR;
			goto jleave;
		}
	}

	memset(&soa6, 0, sizeof(soa6));
	soa6.sin6_family = AF_INET6;
	soa6.sin6_port = htons(portno);
	memcpy(&soa6.sin6_addr, &in6addr_any, sizeof soa6.sin6_addr);

	while(bind(sofd, (struct sockaddr const*)&soa6, sizeof soa6) == -1){
		es = errno;
		if(es != EINTR){
			fprintf(stderr, "Server socket cannot bind: %s\n", strerror(es));
			es = a_EX_OSERR;
			goto jleave;
		}
	}

        memset(&siac, 0, sizeof siac);
        sigemptyset(&siac.sa_mask);

        siac.sa_handler = &a_sig_hdl;
        sigaction(SIGTERM, &siac, NULL);
	/*if(a_verbose)*/
		sigaction(SIGINT, &siac, NULL);

	siac.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &siac, NULL);
	sigaction(SIGCHLD, &siac, NULL);

	argv[0] = "sh";
	argv[1] = cmd;
	argv[2] = "verify";
	argv[3] = nbuf;
	/* argv[4] = privkey or NULL, our trigger */
	argv[5] = sigpool;
	argv[6] = dbuf; /* enckey */
	/*argv[7] = encsig;*/
	argv[8] = NULL;

	while(!a_sig_seen){
		ssize_t rb, i;

		soal = sizeof soa6;
		rb = recvfrom(sofd, dbuf, sizeof(dbuf) -1, 0, (struct sockaddr*)&soa6, &soal);
		if(rb == 0)
			continue;
		if(rb == -1){
			es = errno;
			if(es == EINTR)
				continue;
			fprintf(stderr, "Failed receiving packet: %s\n", strerror(es));
			/* (xxx CONNRESET should trigger firewall rules, so do not care..) */
			if(es != EBADF && es != EINVAL)
				continue;
			es = a_EX_OSERR;
			goto jleave;
		}

		if(inet_ntop(soa6.sin6_family, &soa6.sin6_addr, nbuf, sizeof nbuf) == NULL){
			fprintf(stderr, "IMPL ERROR: cannot inet_ntop() client address after recvfrom(2)\n");
			continue;
		}

		/* TODO Linux kernel iptables xt_recent does not understand a mapped address.
		 * (I opened https://bugzilla.kernel.org/show_bug.cgi?id=219038, but dunno if that is right.) */
		if(strchr(nbuf, '.') != NULL){
			i = (size_t)strlen(nbuf);
			if((size_t)i <= sizeof("::ffff:") -1 + 4){
				fprintf(stderr, "IMPL ERROR: IPv4 mapped address is bogus\n");
				continue;
			}
			i -= sizeof("::ffff:") -1;
			memmove(nbuf, &nbuf[sizeof("::ffff:") -1], i +1);
		}

		/* Default: do not inspect packet, block this IP */
		argv[4] = NULL;

		/* Buffer spaced so excess is error */
		if(rb == (ssize_t)sizeof(dbuf) -1)
			goto jfork;
		dbuf[(size_t)rb] = '\0';

		/* Inspect packet, find enckey and encsig boundaries (for format see a_BUF_LEN #define above) */
		argv[7] = NULL;
		i = 0;
		for(;;){
			char c;

			c = dbuf[(size_t)i];
			if(c != '\n'){
				if(((unsigned)c & 0x80) || iscntrl(c))
					goto jfork;
				if(++i == rb)
					goto jfork;
				continue;
			}

			++i;
			if(argv[7] == NULL){
				c = dbuf[(size_t)i]; /* dbuf is NUL terminated, so never excess */
				if(c != '\n')
					goto jepack;
				dbuf[(size_t)i] = '\0';
				if(++i == rb)
					goto jepack;
				if(rb - i < a_SIG_LEN_MIN)
					goto jfork;
				argv[7] = &dbuf[(size_t)i];
			}else if(i != rb){
jepack:
				if(a_verbose)
					fprintf(stderr, "Invalid packet content\n");
				goto jfork;
			}else
				break;
		}

		/* Hook may inspect packet data and decide further */
		argv[4] = privkey;
jfork:
		if(a_verbose)
			fprintf(stderr, "execv: " a_BIN_SH " %s %s %s %s %s %s %s (%ld bytes input)\n",
				argv[1], argv[2], argv[3],
				(argv[4] != NULL ? argv[4] : ""),
				(argv[4] != NULL ? argv[5] : ""),
				(argv[4] != NULL ? argv[6] : ""),
				(argv[4] != NULL ? argv[7] : ""),
				(argv[4] != NULL ? (long)rb : 0));

		i = a_fork();
		if(i < 0){
			es = (int)-i;
			fprintf(stderr, "Server fork failed: %s\n", strerror(es));
			es = a_EX_OSERR;
			goto jleave;
		}else if(i == 0){
			execv(a_BIN_SH, (char*const*)argv);
			for(;;)
				_exit(a_EX_OSERR);
		}
	}

	es = a_EX_OK;
jleave:
	if(sofd != -1)
		close(sofd);

	return es;
}

static int
a_client(char const *host, char const *port, char const *enckey, char const *encsig){
	char buf[a_BUF_LEN];
	struct addrinfo hints, *aip0, *aip;
	size_t l, yet;
	int sofd, es;

	memset(&hints, 0, sizeof hints);
#undef a_HINTS
#ifndef __OpenBSD__
# define a_HINTS &hints
	hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_NUMERICSERV;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
#else
# define a_HINTS NULL
#endif

	sofd = -1;
	aip0 = NULL;
	es = a_EX_DATAERR;

	l = strlen(enckey);
	if(l >= sizeof(buf) - 2 - 1 -1){
		fprintf(stderr, "Encrypted key is too long\n");
		goto jleave;
	}
	memcpy(buf, enckey, l);
	buf[l] = '\n';
	buf[l + 1] = '\n';
	l += 2;

	yet = strlen(encsig);
	if(yet >= sizeof(buf) - 1 -1 - l){
		fprintf(stderr, "Encrypted signature is too long\n");
		goto jleave;
	}
	memcpy(&buf[l], encsig, yet);
	buf[l += yet] = '\n';
	buf[++l] = '\0';

	es = getaddrinfo(host, port, a_HINTS, &aip0);
#undef a_HINTS
	if(es != 0){
		fprintf(stderr, "DNS lookup failed for: %s:%s %s\n", host, port, gai_strerror(es));
		es = a_EX_NOHOST;
		goto jleave;
	}

	for(aip = aip0; aip != NULL; aip = aip->ai_next){
		for(;;){
			sofd = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
			if(sofd != -1)
				goto jso;
			es = errno;
			if(es != EINTR)
				break;
		}
		fprintf(stderr, "Socket creation failed: %s\n", strerror(es));
	}
	es = a_EX_UNAVAILABLE;
	goto jleave;

jso:
	/* One tick though */
	yet = 0;
	while(l > 0){
		ssize_t wb;

		wb = sendto(sofd, &buf[yet], l, 0, aip->ai_addr, aip->ai_addrlen);
		if(wb == -1){
			es = errno;
			if(es == EINTR)
				continue;
			fprintf(stderr, "Packet transmit failed: %s\n", strerror(es));
			es = a_EX_IOERR;
			goto jleave;
		}
		yet += wb;
		l -= wb;
	}

	es = a_EX_OK;
jleave:
	if(aip0 != NULL)
		freeaddrinfo(aip0);
	if(sofd != -1)
		close(sofd);
	return es;
}

static long
a_fork(void){
	struct timespec ts;
	long i;

	ts.tv_sec = 0;
	ts.tv_nsec = 250000000L;

	for(;;){
		i = fork();
		if(i != -1)
			break;
		i = -(errno);
		if(i == -ENOSYS)
			break;
		nanosleep(&ts, NULL);
	}

	return i;
}

static void
a_sig_hdl(int sig){
	(void)sig;
	a_sig_seen = 1;
}

/* s-itt-mode */
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@xxxxxxxxxxx
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev

[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux