Hello (and finally), Steffen Nurpmeso wrote in <20240714024434.vvSRh10_@steffen%sdaoden.eu>: ... |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 ... |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. So i did that. It became a "more regular thing" while doing that, ie, with a README (and a big fat warning that this thing should be firewall protected), with nicer -h for the script, etc etc. I keep using IPv6 and mapped address except on *BSD, it works nicely on Linux and OpenIndiana. It is only a pity that the new algorithms do not "simply" allow encryption, and that OpenSSH does not reveal a PEM variant of the private key (for "decrypting", then), so that OpenSSL is needed in addition, at all. Anyhow this is much smaller than installing OpenVPN for some knock daemon (if i understood the discussion right), and i use it. Thanks, and ciao, --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)
S - p o r t - k n o c k ======================= A simple port knock implementation. It requires modern OpenSSH (ssh-keygen(1)), openssl(1) and sh(1) implementations. (It actively searches for bash(1) *if* the sh(1) seems insufficient. $OPENSSL= may be passed to dedicate that.) BIG FAT WARNING --------------- This software MUST be protected by suitable firewall rules! Any network packet sent to it causes a sh(1)ell script to run! For example the author uses this Linux firewall rule which effectively allows only one packet per source address per minute (and causes a hour-long block otherwise). # port_knock: input only server if fwcore_has_i port_knock; then : ${FWCORE_PORT_KNOCK:?port_knock needs FWCORE_PORT_KNOCK} if ipaddr_split ap "${FWCORE_PORT_KNOCK}"; then add_rule -p udp --dport ${port} \ -m recent --name port_knock --set \ -m recent --name port_knock \ --rcheck --seconds 60 --reap --hitcount 2 \ -m recent --name alien_super --set -j DROP add_rule -p udp --dport ${port} -j f_m1 fi fi What does this software do? --------------------------- On servers: . If the packets seems sane, . the OpenSSL private key is used to decrypt the random key, . the random key is used to decrypt the signature, . the "ssh-keygen -Y" mechanism is used to find a principal for the signature in the "allowed signers" file. . (If a $MAGIC string is required, the signature is also verified.) . On any stage a failure causes the act_block() action, . upon overall success the act_allow() action is applied. # cd /tmp/ # $CC -o zt s-port-knock-bin.c # head -n1 ~/.ssh/authorized_keys > .Zpub1 # { printf 'uid1 '; cat .Zpub1; } > .Zsigs # echo MAGIC= > .Z.rc # ./s-port-knock.sh create-server-key .Zk # PORT_KNOCK_BIN=./zt PORT_KNOCK_SHELL=/bin/bash PORT_KNOCK_RC=./.Z.rc \ ./s-port-knock.sh start-server -v 10000 s-port-knock.sh \ .Zk-pri.pem .Zsigs On clients: . Creates a signature for a SSH public key via "ssh-keygen -Y", . (if a $MAGIC string is required, includes that in the signature), . encrypts that with a random key, . and encrypts the random key for a dedicated target server via an (its) OpenSSL public key, . sends the resulting packet to that server, . invokes the $PORT_KNOCK_RC act_sent() hook; if that does not return 0, the packet is sent again, etc. # cd /tmp/ # PORT_KNOCK_BIN=./zt PORT_KNOCK_RC=./.Z.rc \ ./s-port-knock.sh knock localhost 10000 .Zk-pub.pem .Zpub1 Build and Install ----------------- gcc -o /usr/sbin/s-port-knock-bin s-port-knock-bin.c cp s-port-knock.sh /usr/bin RELEASES -------- v0.8.0, 2024-07-17: (first release) + Linux (musl, glibc), *BSD: As above. (IPv6 support is assumed.) + OpenIndiana 2024: $ LD_LIBRARY_PATH=/usr/openssl/3.1/lib/amd64:$LD_LIBRARY_PATH \ C_INCLUDE_PATH=/usr/openssl/3.1/include:$C_INCLUDE_PATH \ gcc -o /usr/sbin/s-port-knock-bin s-port-knock-bin.c -lsocket is needed in the environment for the build. You may need to pass OPENSSL=PATH to the s-port-knock* commands. # s-ts-mode
#!/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. # : ${PORT_KNOCK_BIN:=/usr/sbin/s-port-knock-bin} SELF=s-port-knock.sh VERSION=0.8.0 CONTACT='Steffen Nurpmeso <steffen@xxxxxxxxxx>' # syno() { echo >&2 $SELF' ('$VERSION'): simple UDP based port knock client/server' echo >&2 echo >&2 'SYNOPSIS: '$SELF' knock host port pubkey ssh-client-pubkey' echo >&2 ' - knock for ssh-client-pubkey at server (host:port, pubkey).' echo >&2 ' Requires '$PORT_KNOCK_BIN', or bash(1) in $PATH' echo >&2 echo >&2 'SYNOPSIS: '$SELF' create-server-key filename-prefix [rsa[:bits]]' echo >&2 ' - generate *-{pri,pub}.pem; -pub.pem is needed by clients' echo >&2 'SYNOPSIS: '$SELF' start-server [-v] port cmd prikey allowed-signers' echo >&2 ' - cmd is sh(1) script to "verify" with (ie this one); Read README!' echo >&2 ' - With -v one should add --no-close and --output to s-s-d options' echo >&2 ' --start --background --make-pidfile --pidfile XY --exec' echo >&2 'SYNOPSIS: '$SELF' verify IP [prikey allowed-signers enc-key enc-sig]' echo >&2 ' - (called by server); if prikey and allowed-signers (see ssh-keygen)' echo >&2 ' are given, verify enc-key and enc-sig, else just block IP address' echo >&2 echo >&2 '. Set $PORT_KNOCK_BIN environment to replace '$PORT_KNOCK_BIN',' echo >&2 ' $PORT_KNOCK_SHELL (/bin/sh), and $MAGIC to include a "magic string" in sigs.' echo >&2 '. Set $PORT_KNOCK_RC to specify a resource file' echo >&2 ' MAGIC= # (server *verifies* sig additionally only if non-empty!) ' echo >&2 ' act_sent() { echo >&2 "client sent to $1"; return 0; } # !0: retry!' echo >&2 ' act_block() { echo >&2 "server blocking "$1; }' echo >&2 ' act_allow() { echo >&2 "server allowing $1, principal: $2"; }' echo >&2 '. Bugs/Contact via '$CONTACT exit 64 # EX_USAGE } # # 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. : ${RSA_DEFBITS:=2048} : ${RSA_MINBITS:=1024} : ${SHELL:=/bin/sh} : ${OPENSSL:=openssl} : ${TMPDIR:=/tmp} : ${ENV_TMP:="$TMPDIR:$TMP:$TEMP"} # >8 -- 8< # knock: save encrypted key and signature there # verify: fetch enc-key and enc-sig from $DEBUG : ${DEBUG:=} LC_ALL=C EX_DATAERR=65 EX_CANTCREAT=73 EX_TEMPFAIL=75 # For heaven's sake auto-redirect on SunOS/Solaris, and for pipefail, if necessary if [ -z "$__PORT_KNOCK_UP" ]; then if [ $# -eq 0 ] || [ "$1" = -h ] || [ "$1" = --help ]; then syno fi if (set -C -o pipefail) >/dev/null 2>&1; then : elif [ -n "$PORT_KNOCK_SHELL" ]; then __PORT_KNOCK_UP=$PORT_KNOCK_SHELL #printf >&2 'INFO: "set -C -o pipefail" unsupported, redirect via $PORT_KNOCK_SHELL='$__PORT_KNOCK_UP else __PORT_KNOCK_UP=$(command -v bash 2>&1) if [ $? -eq 0 ]; then #printf >&2 'INFO: "set -C -o pipefail" unsupported, redirect via '$__PORT_KNOCK_UP : elif [ -d /usr/xpg4 ]; then if [ x"$SHELL" = x/bin/sh ]; then __PORT_KNOCK_UP=/usr/xpg4/bin/sh PATH=/usr/xpg4/bin:${PATH} #printf >&2 'INFO: SunOS/Solaris, redirect via '$__PORT_KNOCK_UP fi else __PORT_KNOCK_UP= # (should be but whee) fi fi if [ -n "$__PORT_KNOCK_UP" ]; then export __PORT_KNOCK_UP PATH exec "$__PORT_KNOCK_UP" "$0" "$@" fi fi if (set -o pipefail); then # >/dev/null 2>&1; then set -o pipefail else # XXX circumvent via estat=$(exec 3>&1 1>&2; { DOIT; echo $? >&3; } | BLA) echo >&2 'WARNING: shell ('$SHELL') does not support "set -o pipefail", this HIDES ERRORS' echo >&2 'Run with a more modern shell like so: SHELL '$0' '$* fi if (set -C) >/dev/null 2>&1; then :; else echo >&2 'ERROR: shell does not support noclobber option aka set -C' echo >&2 'ERROR: please run with a more modern shell like so: SHELL '$0' '$* exit $EX_TEMPFAIL fi umask 077 # $PORT_KNOCK_RC defaults : ${MAGIC:=} act_block() { echo >&2 'blocking '$1; } act_allow() { echo >&2 'allowing '$1', principal: '$2; } __wW= act_sent() { # (specific to how my firewall works and assumes we were blocked before OR now) echo >&1 'Pinging '$1 ping=ping [ "$1" != "${1##*::}" ] && command -v ping6 >/dev/null 2>&1 && ping=ping6 if [ -z "$__wW" ]; then 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 fi sleep 1 $ping -c1 $__wW "$1" && return 0 echo 'Did not work out, sleeping 60 seconds: '$ping -c1 $__wW "$1" sleep 60 return 1 } 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 ( 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 set +e 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 ' PRINCIPAL(ie, email address) KEYTYPE PUBKEY [COMMENT]' echo '(ie, authorized_keys format but prefixed by an PRINCIPAL)' ;; 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 kexe=$(command -v bash 2>/dev/null) if [ $? -ne 0 ]; then echo >&2 'Knocking requires bash in $PATH; or '$PORT_KNOCK_BIN syno fi 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 ] || [ -z "$ek" ]; then echo >&2 'Failed to pkeyutl encrypt' exit $EX_TEMPFAIL fi es=$(printf '%s' "$MAGIC" | ssh-keygen -Y sign -n pokn -f "$5" | 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 ] || [ -z "$es" ]; then echo >&2 'Failed to encrypt signature' exit $EX_TEMPFAIL fi if [ -n "$DEBUG" ]; then set -e { echo "$ek"; echo "$es"; } > "$DEBUG" else if [ -z "$kpk" ]; then tmp_file_new printf "%s\n\n%s\n" "$ek" "$es" > "$tmp_file" fi while :; do printf 'knocking ... ' if [ -n "$kpk" ]; then "$kexe" client "$3" "$2" "$ek" "$es" else </dev/null bash -c ' set -e cat "'"$tmp_file"'" > /dev/udp/"'"$2"'"/"'"$3"'" ' fi [ $? -ne 0 ] && break echo 'done' act_sent "$2" && break 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 ossl() 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 pokn -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 line: '$* 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> /* 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 /* Used if $PORT_KNOCK_SHELL is not set */ #define a_BIN_SH "/bin/sh" /* Whether we should open distinct IPv4 and IPv6 sockets */ #undef a_DISTINCT_SOCKS #if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ # define a_DISTINCT_SOCKS #endif /* >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(unsigned short 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 chdir, [chroot], pledge/unveil */ es = srv ? a_server((unsigned short)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 client port host enckey encsig\n", prog, prog); goto jleave; } static int a_server(unsigned short portno, char const *cmd, char const *privkey, char const *sigpool){ char dbuf[a_BUF_LEN], nbuf[INET6_ADDRSTRLEN +1]; char const *myshell, *argv[9]; fd_set rfds; struct sigaction siac; socklen_t soal; #ifdef a_DISTINCT_SOCKS struct sockaddr_in soa4; int sofd4; #endif struct sockaddr_in6 soa6; int sofd6, es; if(!a_verbose) freopen("/dev/null", "w", stderr); portno = htons(portno); sofd6 = -1; #ifdef a_DISTINCT_SOCKS while((sofd4 = socket(AF_INET, SOCK_DGRAM, 0)) == -1){ es = errno; if(es == EINTR) continue; if(es == EAFNOSUPPORT){ fprintf(stderr, "IPv4 socket unsupported, skipping this\n"); break; } fprintf(stderr, "IPv4 server socket creation failed: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; } if(sofd4 != -1){ soa4.sin_family = AF_INET; soa4.sin_port = portno; soa4.sin_addr.s_addr = INADDR_ANY; while(bind(sofd4, (struct sockaddr const*)&soa4, sizeof soa4) == -1){ es = errno; if(es != EINTR){ fprintf(stderr, "IPv4 server socket cannot bind: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; } } } #endif while((sofd6 = socket(AF_INET6, SOCK_DGRAM, 0)) == -1){ es = errno; if(es == EINTR) continue; #ifndef a_DISTINCT_SOCKS if(es == EAFNOSUPPORT){ fprintf(stderr, "IPv6 socket unsupported, skipping this\n"); break; } #endif fprintf(stderr, "IPv6 server socket creation failed: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; } if(sofd6 != -1){ #ifdef a_DISTINCT_SOCKS # ifdef IPV6_V6ONLY int one; one = 1; if(setsockopt(sofd6, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&one, sizeof(one)) == -1){ fprintf(stderr, "IPv6 cannot set server socket option V6ONLY: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; } # endif #endif soa6.sin6_family = AF_INET6; soa6.sin6_port = portno; memcpy(&soa6.sin6_addr, &in6addr_any, sizeof soa6.sin6_addr); while(bind(sofd6, (struct sockaddr const*)&soa6, sizeof soa6) == -1){ es = errno; if(es != EINTR){ fprintf(stderr, "IPv6 server socket cannot bind: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; } } } #ifdef a_DISTINCT_SOCKS else if(sofd4 == -1){ fprintf(stderr, "Cannot create any server sockets, bailing out\n"); es = a_EX_OSERR; goto jleave; } #endif memset(&siac, 0, sizeof siac); sigemptyset(&siac.sa_mask); siac.sa_handler = &a_sig_hdl; sigaction(SIGHUP, &siac, NULL); /*if(a_verbose)*/ sigaction(SIGINT, &siac, NULL); sigaction(SIGTERM, &siac, NULL); siac.sa_handler = SIG_IGN; sigaction(SIGCHLD, &siac, NULL); sigaction(SIGPIPE, &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; myshell = getenv("PORT_KNOCK_SHELL"); if(myshell == NULL) myshell = a_BIN_SH; while(!a_sig_seen){ ssize_t rb, i; void *sadp; struct sockaddr *sap; int fd; FD_ZERO(&rfds); #ifdef a_DISTINCT_SOCKS if(sofd4 != -1) FD_SET(sofd4, &rfds); if(sofd6 != -1) #endif FD_SET(sofd6, &rfds); es = select(( #ifdef a_DISTINCT_SOCKS (sofd4 > sofd6) ? sofd4 : #endif sofd6) + 1, &rfds, NULL, NULL, NULL); if(es == -1){ es = errno; if(es == EINTR) continue; fprintf(stderr, "Selection on server socket I/O failed: %s\n", strerror(es)); es = a_EX_OSERR; goto jleave; }else if(es == 0) /* ?? */ continue; #ifdef a_DISTINCT_SOCKS if(sofd4 != -1 && FD_ISSET(sofd4, &rfds)){ FD_CLR(sofd4, &rfds); sadp = &soa4.sin_addr; sap = (struct sockaddr*)&soa4; soal = sizeof soa4; fd = sofd4; }else if(sofd6 != -1) jNext_socket: #endif { if(FD_ISSET(sofd6, &rfds)){ FD_CLR(sofd6, &rfds); sadp = &soa6.sin6_addr; sap = (struct sockaddr*)&soa6; soal = sizeof soa6; fd = sofd6; }else continue; } #ifdef a_DISTINCT_SOCKS else /* pacify clang */ continue; #endif rb = recvfrom(fd, dbuf, sizeof(dbuf) -1, 0, sap, &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(sap->sa_family, sadp, nbuf, sizeof nbuf) == NULL){ fprintf(stderr, "IMPL ERROR: cannot inet_ntop() peer address after recvfrom(2)\n"); continue; } /* Linux kernel iptables xt_recent 6.1.98/6.6.40 do not deal with mapped addresses * (https://bugzilla.kernel.org/show_bug.cgi?id=219038) */ if(sap->sa_family == AF_INET6 && strchr(nbuf, '.') != NULL){ i = (size_t)strlen(nbuf); if((size_t)i <= sizeof("::ffff:") -1 + 4+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, only 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: %s %s %s %s %s %s %s %s (%ld bytes input)\n", myshell, 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); } #ifdef a_DISTINCT_SOCKS if(fd != sofd6 && sofd6 != -1) goto jNext_socket; #endif } es = a_EX_OK; jleave: if(sofd6 != -1) close(sofd6); #ifdef a_DISTINCT_SOCKS if(sofd4 != -1) close(sofd4); #endif 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); hints.ai_flags = AI_NUMERICSERV #ifdef AI_ADDRCONFIG | AI_ADDRCONFIG #endif #if /* always, we do not care who gives !defined a_DISTINCT_SOCKS &&*/ defined AI_V4MAPPED | AI_V4MAPPED #endif ; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; 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, &hints, &aip0); 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