Proxying commands across a SSH connection - linux

I'd like to be able to "spoof" certain commands on my machine, actually invoking them on a remote system. For example. Whenever I run:
cmd options
I'd like the actual command to be:
ssh user#host cmd options
Ideally I'd like to have a folder called spoof, add it to my PATH, and have an executable in there called cmd which does the spoofing. If I have a lot of commands, this could get tedious. Anyone have ideas of a good way to go about this? Such that I can add and remove a lot of commands in the future? And, I'd like to be able to pass all the arguments exactly (or as exact as possible) and every single command I want to spoof would just have the ssh user#host in front of it.
The reason for this is I'm running a container (specifically singularity) on my machine, and there are certain commands I don't really want to containerize, but still want to run from within the container. I've found I can get the functionality I want by just appending the ssh in front of it. Examples are sbatch and matlab which are a pain to containerize and I'm fine with just using ssh to call them. Files that these programs use are written to a bind point so the host machine can see them just fine.

The following script can be hardlinked under all the names of commands you wish to transparently proxy:
#!/usr/bin/env bash
printf -v str '%q ' "${0##*/}" "$#"
ssh host "$str"
SSH combines all its arguments into a single string, which is then executed by a remote shell. To ensure that the remote arguments are identical to the local one, the values need to be escaped; otherwise, somecommand "hello world" and somecommand "hello" "world" can be represented identically over-the-wire.
In an appropriately extended printf (including both bash and ksh implementations), %q is replaced with an escaped form of the corresponding value, which will be evaled back to the original (literal) text by if interpreted later.
printf -v varname stores the output of printf in a variable named varname without the overhead/inefficiency of a command substitutions. (In ksh93, varname=$(printf ...) is optimized to skip subshell overhead, so this is not necessary there).
$0 evaluates to argv[0], which is by convention the name of the command currently being run. (This can be overridden, but you trust your users to behave reasonably... right?)
${0##*/} is a parameter expansion which returns only content after the last / in $0 (should it in fact contain any slashes; otherwise, the original value is used unmodified).
"$#" refers to the exact argument vector passed to your script.

Related

Edit remote file with variables

I've write this script but it does not works:
E_OPT=" some_host(ro,insecure) some_host2(ro,insecure)"
echo -n "Insert path to export [ ex: /path/test ]"
read PATH
FINAL=$PATH$E_OPT
ssh SERVER echo "$FINAL" >> file
or
ssh SERVER echo '$FINAL >> file'
or
ssh SERVER 'echo "$FINAL" >> file'
How can I pass text in variable to append in remote files?
There are a couple of problems here. The first is with read PATH. The variable PATH is one of many that have special meaning to the system: it defines where to look for executables (e.g. for commands). As soon as you redefine it as something else, the system will be unable to find executables like ssh, so commands will start to fail. Solution: use lowercase or mixed-case variable names to avoid conflicts with any of the special-meaning variables (which are all uppercase).
Second, all of your attempts at quoting are wrong. The command is going to go through two levels of shell parsing: first on the local computer (where you want the variable $FINAL -- or better $final -- to be expanded), and then on the remote server (where you want the parentheses to be in quotes, so they don't cause shell syntax errors). This means you need two levels of quoting: an outer leven that gets parsed & removed by the local shell, and a second level that gets parsed & removed by the remote shell. Variable expansion is only done in double-quotes, not single-quotes, so the outer level has to be double-quotes. The inner level could be either, but single-quotes are going to be easiest:
ssh SERVER "echo '$final' >> file"
Now, it may look like that $final variable is in single-quotes so it won't get expanded; but quotes don't nest! As far as the local shell is concerned, that's a double-quoted string that happens to contain some single-quotes or apostrophes or something that doesn't really matter. When the remote shell receives the command, the variable has been substituted and the outer quotes removed, so it looks like this:
echo '/some/path some_host(ro,insecure) some_host2(ro,insecure)' >> file
...which is what you want.
You must export the FINAL variable, besides, you also need to execute your script with a dot at the beginning, like:
. server-script.sh
This will evaluate the variables on the local bash, instead of a sub-shell.

bash + Linux + how to ignore the character "!"

I want to send little script to remote machine by ssh
the script is
#!/bin/bash
sleep 1
reboot
but I get event not found - because the "!"
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
-bash: !/bin/bash\nsleep: event not found
how to ignore the "!" char so script will so send successfully by ssh?
remark I cant use "\" before the "!" because I get
more /tmp/file
#\!/bin/bash
sleep 1
Use set +H before your command to disable ! style history substitution:
set +H
ssh 183.34.4.9 "echo -e '#!/bin/bash\nsleep 1\reboot>'/tmp/file"
# enable hostory expnsion again
set -H
I think your command line is not well formated. You can send this:
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\nreboot">/tmp/file'
When I say "not well formated" I mean you put ">" inside the "echo" and you forgot to add "n" before "reboot", and you put "\reboot", wich will be interpreted as "CR" (carriage return) followed by "eboot" command (which I don't think that exists).
But what did the trick here is to invert the comas changing (') with (") and viceversa.
Bash is running interactively (which means that you are feeding commands to it from the standard input and not exec(2)ing a command from a shell script) so you don't need to include the line #!/bin/bash in that case (even more, bash should just ignore it, but not the included bang, as it is part of the active history mechanism)
But why? the first two characters in an executable file (any file capable of being exec(2)ed from secondary storage, not your case) have a special meaning (for the kernel and for the shell): they are the magic number that identifies the kind of executable file the kernel is loading. This allows the kernel to select the proper executable loading routines depending on the binary executable format (and what allows you for example to execute BSD programs in linux kernels, and viceversa)
A special value for this magic numbers is composed by the two characters # and ! (in that order) that forces the kernel to read the complete first line of that file and load the executable file specified in that line instead, allowing you to execute shell scripts for different interpreters directly from the command line. And it is done on purpose, as the # character is commonly in shell script parlance a comment character. This only happens when the shell that is interpreting the commands is not an interactive shell. When the shell loads a script with those characters, it normally reads the first line also to check if it has the #! mark and load the proper interpreter, by replicating the kernel function that does this. Despite of being a comment for the shell, it does this to allow to treat as executables files that are not stored on secondary storage (the only ones the exec(2) system call can deal with), but coming from stdin (as happens to yours).
As your shell is running interactively and you do want to execute its commands without a shell change, you don't need that line and can completely eliminate it without having to disable the bang character.
Sorry, but the solution given about executing the shell with -H option will probably not be viable, as the shell executing the commands is the login shell in the target machine, so you cannot provide specific parameters to it (parameters are selected by the login(8) program and normally don't include arbitrary parameters like -H).
The best solution is to fully eliminate the #!/bin/bash line, as you are not going to exec(2) that program in the target. In case you want to select the shell from the input line (case the user has a different shell installed as login shell), it is better to invoke the wanted shell in the command line and pass it (through stdin, or making it read the shell script as a file) the shell commands you wan to execute (but again, without the #! line).
NOTE
Its important to ensure you'll execute the whole thing, so it's best to pass all the script contents in the destination target, and once assured you have passed the whole thing to execute it as a whole. Then your #! first line will be properly processed, as the executable will be run by means of an exec(2) made from the kernel.
Example:
DIRECTORY=/bla/bla
FILE=/path/to/file
OUTPUT=/path/to/output
# this is the command we want to pass through the line
cat <<EOF | ssh user#target "cat >>/tmp/shell.sh"
cd $DIRECTORY
foo $FILE >$OUTPUT
exit 0
EOF
# we have copied the script file in a remote /tmp/shell.sh
# and we are sure it has passed correctly, so it's ready
# for local execution there.
# now, execute it.
# the remote shell won't be interactive, and you'll ensure that it is /bin/bash
ssh user#target "/bin/bash /tmp/shell.sh" >remote_shell.out
A more sophisticate system is one that allows to to sign the shell script before sending, and verify the script signature before executing it, so you are protected against possible trojan horse attacks. But this is out of scope on this explanation.
Another alternative is to use the batch(2) command remotely and pass it all the commands you want executed. you'll get a sessionless executing environment, more suitable to the task you are demanding (despite the fact that you'll get the script output by email to the target user running the script)
Interactively, beware that ! triggers history expansion inside double quotes
from here: https://riptutorial.com/bash/example/2465/quoting-literal-text
my recommended solution is to use single quotes to define the string (and either escape single quotes \' or use double quotes " within the string):
ssh 183.34.4.9 'echo -e "#!/bin/bash\nsleep 1\reboot>"/tmp/file'

run a remote bash script with arguments with ssh

I am unable to run a remote shell script located on "admin" server with arguments.
ssh koliwada#admin "~/bin/addautomaps $groupentry $homeentry $ticket"
"groupentry" and "homeentry" are as follows
user1:*:52940:OWNER-user1
user1 -rw,intr,hard,rsize=32768,wsize=32768 basinas01:/ifs/basinas01/home/&
the script is located at ~/bin/addautomaps in admin server.
I see the error,
tput: No value for $TERM and no -T specified
I also see the arguments also are not passed correctly.
I also tried using "ssh -t ..." but that doesnt work.
Answering your questions in reverse order (or most serious to least serious).
Your problem with the arguments (with spaces) not being passed correctly is that while you are quoting the command string locally you aren't quoting them when they are actually run by the remote machine.
That is you are generating a single string with the variables expanded but nothing tells the remote system not to split the expanded values on spaces.
The fix for that is that you need to quote the arguments inside the command for the remote shell as well as the entire string for ssh.
My answer here might help explain some (it is a similar issue).
The tput "issue" is likely just a warning that you can probably ignore if you don't care about the colorized/stylized/etc. output that tput is likely being used to create. You could also try forcing a value for $TERM on the remote side like ssh ... "export TERM=dumb; ..." or something like that to silence it.

Alias definition of multiple commands after ssh

I'm logging in and out of a remote machine many times a day (through ssh) and I'd like to shorten a bit the whole procedure. I've added an alias in my .bashrc and .profile that looks like:
alias connect='ssh -XC username#remotemachine && cd /far/away/location/that/takes/time/to/get/to/;'
My problem is that when I write connect, I first get to the location in cause (on my local machine) and then the ssh connection takes place. How can this be? I've thought that by using "&&" the second command will be run only after the first one is successful. After the ssh command is successful, the .profile/.bashrc are loaded anew, before the second part of the alias is successfully executed?
For the ssh specifically, you're looking for the following:
ssh -t username#remotemachine "cd /path/you/want ; bash"
Using "&&" or even ";" normally will execute the commands in the shell that you're currently in. It's like if you're programming and make a function call and then have another line that you want to effect what happens in the function-- it doesn't work because it's essentially in a different scope.
For sequence of commands :
Try this (Using ;) :
alias cmd='command1;command2;command3;'
Use of '&&' instead of ';' -
The && makes it only execute subsequent commands if the previous returns successful.

In my command-line, why does echo $0 return "-"?

When I type echo $0 I see -
I expect to see bash or some filename, what does it mean if I just get a "-"?
A hyphen in front of $0 means that this program is a login shell.
note: $0 does not always contain accurate path to the running executable as there is a way to override it when calling execve(2).
I get '-bash', a few weeks ago, I played with modifying a process name visible when you run ps or top/htop or echo $0. To answer you question directly, I don't think it means anything. Echo is a built-in function of bash, so when it checks the arguments list, bash is actually doing the checking, and seeing itself there.
Your intuition is correct, if you wrote echo $0 in a script file, and ran that, you would see the script's filename.
So based on one of your comments, you're really want to know how to determine what shell you're running; you assumed $0 was the solution, and asked about that, but as you've seen $0 won't reliably tell you what you need to know.
If you're running bash, then several unexported variables will be set, including $BASH_VERSION. If you're running tcsh, then the shell variables $tcsh and $version will be set. (Note that $version is an excessively generic name; I've run into problems where some system-wide startup script sets it and clobbers the tcsh-specific variable. But $tcsh should be reliable.)
The real problem, though, is that bash and tcsh syntax are mostly incompatible. It might be possible to write a script that can execute when invoked (via . or source) from either tcsh or bash, but it would be difficult and ugly.
The usual approach is to have separate setup files, one for each shell you use. For example, if you're running bash you might run
. ~/setup.bash
or
. ~/setup.sh
and if you're running tcsh you might run
source ~/setup.tcsh
or
source ~/setup.csh
The .sh or .csh versions refer to the ancestors of both shells; it makes sense to use those suffixes if you're not using any bash-specific or tcsh-specific features.
But that requires knowing which shell you're running.
You could probably set up an alias in your .cshrc, .tcshrc, or.login, and an alias or function in your.profile,.bash_profile, or.bashrc` that will invoke whichever script you need.
Or if you want to do the setup every time you login, or every time you start a new interactive shell, you can put the commands directly in the appropriate shell startup file(s). Of course the commands will be different for tcsh vs. bash.

Resources