What I want to do SHOULD be fairly straight forward but a whole day lost to this already I'm reworking the question a bit to help focus:
The overall goal is to have a bash based "server" that, using either socat
(preferred) or ncat
(should work well also), receives connections from one or more "clients", processes data from the clients and, when appropriate, returns a response to clients. This script also needs to be able to handle a few sundry "system management" tasks that relate to all this, and this is only mentioned because redirecting the scripts in and out would be problematic.
The overall, quite persistent problem is getting bidirectional data flow to and from the clients and this bash script simultaneously. One or the other at any one time appears to be relatively easy, but both at the same time I have not achieved yet, and when I do, this question is solved.
Early Research:
This question seems to ask the right question, but it garnered no answers that would help me.
The closest I've found so far was in this answer, but it doesn't do what I want to do. That one hands both / all IO to the connecting client, thus making it the server... What I like about it is that it actually DOES set up ncat's input and output in a useful way. Thus, the ideas contained therein were interesting enough to try and I did. However, they use a pipe for input and an > based redirect of output and in retrospect perhaps I should pursue this strategy a bit more.
However, I also tried this answer which also seems very promising, but also doesn't work.
In the comments and proposed answer it became clear that there's confusion over the terms stdin
and stdout
, mistaking these to mean stdin
and stdout
of the bash script itself. It didn't help that I was using stdin to get user input from the keyboard to help show sending to clients as well as receiving.
Of course, each program has their own view of what is standard in or out and in my writing I have only ever meant stdout
and stdin
from the point of view of socat
or ncat
.
In "pseudo code," here's an example of what the finished code might look like:
while read line
do
# ...Process line based on it's contents.
#
# When a response is needed, stored in a variable
# called reply, then this might happen:
if [ -n "$reply" ] ;
then
echo "$reply" > socat_stdin
fi
done < socat_stdout
Along the way I've tried many versions of this:
while read line
do
echo "$line"
echo "Back at you: $line" >&3
done < $( exec 3> >(ncat -l -k $port) )
By now I've tried all manner of redirecting to socat
's stdin
using < and in some configurations of that it then won't start saying it doesn't have two connections specified. Obviously I haven't hit the right combination yet.
Here's where I left off last night:
#!/bin/bash
#
port=7654
inFile=/tmp/in
outFile=/tmp/out
logFile=/tmp/log
exec /bin/socat -U TCP-LISTEN:$port,fork EXEC:"tail -f $inFile" > $outFile 2>$logFile &
while read line
do
if [ -z "$line" ] ;
then
echo "EMPTY LINE!"
else
echo "user: $line"
echo "Back at you: $line" > "$inFile"
fi
done < $(tail -f "$outFile")
If I could figure out how to get the exec (not EXEC!) to perform a compound command I'd like to try putting a tail -f
in front of it and pipe in the responses from the server to the client(s).
In diagnosing that, lsof
shows, among many other things:
socat 3588549 root 0r CHR 1,3 0t0 4 /dev/null
...YES, it persistently has an open file descriptor to /dev/null?! I think this is a clear sign "I'm doing it wrong!" And, indeed, in this above scenario, clients are muted. Hmmm...
Any questions about this, just ask, etc... Thanks for the help...
...For now the question remains; how is this done?
In working on this I found reference in the literature to this file: /usr/share/doc/socat/mail.sh
For anyone curious about this file it points the way BUT requires TWO programs to do this, or a second iteration; it'd be GREAT to eliminate one and I'm looking to perhaps adapt Phillipe's named pipe suggestion.
Here are that file's contents - it's fairly short:
#! /bin/sh
# source: mail.sh
# Copyright Gerhard Rieger and contributors (see file CHANGES)
# Published under the GNU General Public License V.2, see file COPYING
#set -vx
# This is an example for a shell script that can be fed to socat with exec.
# Its clue is that it does not use stdin/stdout for communication with socat,
# so you may feed the mail message via stdin to the script. The message should
# contain appropriate mail headers without continuation lines.
# socat establishes the connection to the SMTP server; the script performs the
# SMTP dialog and afterwards transfers the message body to the server.
# Lines with only a dot are not permitted - use two dots as escape.
# This script supports multiline answers from server, but not much more yet.
# Usage: cat message.txt |socat exec:"mail.sh target@domain.com",fdin=3,fdout=4 tcp:mail.relay.org:25,crlf
while [ "$1" ]; do
case "$1" in
-f) shift; mailfrom="$1"; shift;;
*) break;;
esac
done
rcptto="$1"
[ -z "$1" ] && rcptto="root@loopback"
#server=$(expr "$rcptto" : '[^@]*@\(.*\)')
[ -z "$mailfrom" ] && mailfrom="$USER@$(hostname)"
# this function waits for a complete server message, checks if its status
# is in the permitted range (terminates session if not), and returns.
mail_chat () {
local cmd="$1"
local errlevel="$2"; [ -z "$errlevel" ] && errlevel=300
if [ "$cmd" ]; then echo "> $cmd" >&2; fi
if [ -n "$cmd" ]; then echo "$cmd" >&4; fi
while read status message <&3;
(
case "$status" in
[0-9][0-9][0-9]-*) exit 0;;
[0-9][0-9][0-9]*) exit 1;;
*) exit 1;;
esac
)
do :; done
if [ -z "$status" ]; then echo smtp connection failed >&2; exit; fi
echo "< $status $message" >&2
if [ "$status" -ge "$errlevel" ]; then
echo $message >&2
echo "QUIT" >&4; exit 1
fi
}
# expect server greeting
mail_chat
#...more calls to the mail_chat function...
Indeed, the socat developers provided a major clue - hard to find, though! - with their file provided with my distribution:
/usr/share/doc/socat/mail.sh
In digging in and trying it, I got it working with something like this:
ONE script, uses functions thus:
function startSocat()
{
# ... check for free port, etc
socat exec:"${BASH_SOURCE[0]} -SD",fdin=3,fdout=4 TCP-LISTEN:$port,fork
# -SD indicates, "socat-daemon"
#
# The above tells it to start this same code, pass it -SD and connect to it
# using file descriptors 3 and 4 and, of course where to listen, etc.
}
function socatLoop
{
while read line <&3;
do
# Process the read in line - in variable line!
done
}
function replyToClient()
{
echo "$@" >&4
}
And that pretty much covers all you really need do. ... I trust the reader can fairly easily figure out how to call these but to be explicit:
Of course, if you want to orchestrate private dialogues with an individual client, that's more work on your part, but not that hard now that you know the basics!