Enlico
Enlico

Reputation: 28500

Can I pass en entire 'bash -c "something"' to socat as EXEC address?

I open one terminal, and execute

socat TCP-LISTEN:12345,fork -

then I open another terminal and execute

socat TCP-CONNECT:127.0.0.1:12345 EXEC:'echo ciao'

which results in printing one line containing ciao in the first terminal.

So far so good, but what if I want to execute a script written inline?

I thought I could do like this in the second terminal:

socat TCP-CONNECT:127.0.0.1:12345 EXEC:'bash -c "echo ciao"'

but this results in the first terminal only receiving a newline character.

What am I missing? Where are the 4 characters ciao getting lost?

Is it just me misunderstanding how the shell is supposed to work in this circumstance, or does the observed behavior depend on how socat API is designed?


I know I can put the Bash script in a file, make it an executable available on the PATH, and use EXEC:that-script.

I've also understood, reading the manual, that there's the SHELL for of address, so one can do:

socat TCP-CONNECT:127.0.0.1:12345 SHELL:"for ((i=0;i<10;i++)); do echo ciao; sleep 1; done"

but I'm still curious as to what's wrong with the bash -c "stuff" syntax.

Upvotes: 4

Views: 139

Answers (2)

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70987

0. Preamble

When I speak about 1st window, I speak about another terminal window in which you've been run:

   socat TCP-LISTEN:12345,fork -

0.1. Strictly using EXEC:"bash -c '...'" as requested

Yes, you could do this (based on Philippe's comment):

command="for ((i=0;i<5;i++));do printf '%4d %s\n' \$i ciao;sleep .25;done"

Test:

bash -c "$command"
   0 ciao
   1 ciao
   2 ciao
   3 ciao
   4 ciao

with 250 milliseconds between each lines...

EXEC:

socat TCP-CONNECT:127.0.0.1:12345 EXEC:"bash -c \"${command//[\' ]/\\\\&}\""

Will do the job!

But this

  • is very fragile! You can't use double quote (") in your code and you may encounter some pain debugging your command string!
  • will become tricky using OPTIONS to socat's EXEC...

1. Using socat, with SYSTEM:

You have to write POSIX shell code!! You could use for overall strings manipulation (putting function to string by using declare -f), so confine your script in a function, but respecting POSIX syntax!

Here is a sample using shared variable ($somevar) and some arguments to the function.

someFunc() { 
    i=0
    while [ $i -lt ${2:-5} ]; do
        printf ' - %2d %s %s\n' $i "$1" "$somevar"
        sleep ${3:-.3}
        i=$((i+1))
    done
}

somevar='baz boo.'

socat TCP-CONNECT:127.0.0.1:12345 SYSTEM:"$(declare -f someFunc
              );somevar=${somevar// /\\\\ } someFunc Foo\\\\ bar 8 .5;"

Then you will see in 1st window, with an interval of an half second on each line:

 -  0 Foo bar baz boo.
 -  1 Foo bar baz boo.
 -  2 Foo bar baz boo.
 -  3 Foo bar baz boo.
 -  4 Foo bar baz boo.
 -  5 Foo bar baz boo.
 -  6 Foo bar baz boo.
 -  7 Foo bar baz boo.

1.1. Little function for testing your command:

testMyCmd() { ${2:-busybox sh} -c "$(echo -e "$1")" ;}

Then

testMyCmd "$(declare -f someFunc);somevar=${somevar// /\\\\ } someFunc Foo\\\\ bar 4 .2" dash
 -  0 Foo bar baz boo.
 -  1 Foo bar baz boo.
 -  2 Foo bar baz boo.
 -  3 Foo bar baz boo.

1.2 This work with interactive command too

someFunc() { 
    read -p 'Enter a number: ' i
    l=0
    while [ $i -gt 0 ]; do
        i=$((i-1))
        l=$((l+1))
        echo Loop $l.
        sleep .2
    done
}
socat TCP-CONNECT:127.0.0.1:12345 SYSTEM:"$(declare -f someFunc
      );someFunc;",pty,stderr

This will prompt in 1st window: Enter a number: . If you hit a number in 1st window, command will continue and exec requested number of loop.

2. Using pure

Under ; you don't need to use netcat or socat for this! You could use pseudo /dev/tcp network folder by:

for ((i=0;i<10;i++)); do echo ciao; sleep 1; done >/dev/tcp/127.0.0.1/12345

2.1. For interactive command (without pty):

someFunc() { 
    printf 'Enter a number: '
    read i
    l=0
    while [ $i -gt 0 ]; do
        i=$((i-1))
        l=$((l+1))
        echo Loop $l.
        sleep .2
    done
}
someFunc >/dev/tcp/127.0.0.1/12345 <&1 2>&1

2.2. Same, with pseudo pty emulation by using script:

This use script which is not , but more commonly installed on Linux systems than socat.

someFunc() { 
    read -p 'Enter a number: ' i
    l=0
    while [ $i -gt 0 ]; do
        i=$((i-1))
        l=$((l+1))
        echo Loop $l.
        sleep .2
    done
}
script -qf /dev/null -c "$(declare -f someFunc
       );someFunc" >/dev/tcp/127.0.0.1/12345 2>&1 <&1

3. Simplier way, use a dedicated script for this.

This way is clearly the most robust, as socat EXEC was done for this kind of use!

But as you are already writting your own script, maybe are you trying to avoid multiple files!!

For this, you could prepare your script to be able to call himself as socat executable:

#!/bin/bash

someFunc() {
    local _i _l _str=${1:-Some string}
    read -p 'Enter a number ' -r _i
    for ((_l=1;_l<=_i;_l++)){
        printf ' - %2d %s\n' $_l "$_str"
        sleep .25
    }
}
otherFunc() {
    local a
    for a; do
        [[ -f /proc/$a ]] &&
            printf '%s %-12s: %s\n' $HOSTNAME $a "$(</proc/$a)"
    done
}
rbash() { bash -i ;}

if [[ $1 == doIt ]]; then
   shift
   "$@" 
   exit 0
fi

if [[ $1 == --script ]] && [[ $2 ]] && declare -f $2 &>/dev/null; then
    shift 1
    script -qf /dev/null -c "$0 doIt ${*// /\\ }" \
        >/dev/tcp/127.0.0.1/12345 2>&1 <&1
elif [[ $1 ]] && declare -f $1 &>/dev/null; then
    socat TCP-CONNECT:127.0.0.1:12345 EXEC:"$0 doIt ${*// /\\\\ },pty,stderr"
else
    echo "Command '$1' unknown."
fi

Save this in a script file named sendMyFunc, (give him execution rights: chmod +x sendMyFunc, then

./sendMyFunc.sh someFunc "Foo bar"

Will prompt Enter a number in 1st window,

3
 -  1 Foo bar
 -  2 Foo bar
 -  3 Foo bar

and do the job.

./sendMyFunc.sh otherFunc uptime loadavg

Will print two lines in 1st window:

springfield loadavg     : 2.30 1.92 1.86 1/1922 2695597
springfield uptime      : 5987838.21 17133210.42
./sendMyFunc.sh --script otherFunc uptime loadavg

Will do same, but using script instead of socat.

./sendMyFunc.sh --script rbash

Use it at your own risk!!

Upvotes: 2

meuh
meuh

Reputation: 12255

I did some experiments a while ago using strace to see what got passed on to the exec() system call, for SYSTEM:. In summary, it

  • drops single quotes and double-quotes,
  • keeps \" with no change
  • keeps \n as \n
  • converts newline into \n
  • keeps real tabs
  • ends on :
  • calls execve("/bin/sh", ["sh", "-c", "the command and args"], env)

For EXEC: it seems similar, but finishes by splitting the string on spaces into words, and passing the words as the argv array, with the first word also being used as the program to exec.

So something like SYSTEM:"dd 'conv=sync bs=2'" (where the 2 args should (wrongly) stay as one string) becomes ok:

execve("/bin/sh", ["sh", "-c", "dd conv=sync bs=2"], env)

and EXEC:"dd 'conv=sync bs=2'" becomes

execve("/usr/bin/dd", ["dd", "conv=sync", "bs=2"], env)

The following shows what string is passed to execve() for various examples:

 SYSTEM:'set -v;echo "abc";'
  "set -v;echo abc;"
 SYSTEM:'set -v;echo \"abc\";'
  "set -v;echo \"abc\";"
 SYSTEM:"set -v;echo 'abc';"
  "set -v;echo abc;"
 SYSTEM:'echo abc\ndef  # <- real newline
 echo pqr   def'    # <-tab char
  "echo abc\ndef\necho pqr\tdef"
 SYSTEM:"echo abc\ndef  # <- real newline. (as above with "")
 echo pqr   def"    # <-tab char
  "echo abc\ndef\necho pqr\tdef"

The sources are xio-exec.c with the call to nestlex(), which is here. I haven't tried to follow the code too much, but my tests seem coherent with the comments there.

Upvotes: 3

Related Questions